[英]Correct way to use HttpContext.Current.User with async await
I am working with async actions and use the HttpContext.Current.User like this我正在处理异步操作并像这样使用 HttpContext.Current.User
public class UserService : IUserService
{
public ILocPrincipal Current
{
get { return HttpContext.Current.User as ILocPrincipal; }
}
}
public class ChannelService : IDisposable
{
// In the service layer
public ChannelService()
: this(new Entities.LocDbContext(), new UserService())
{
}
public ChannelService(Entities.LocDbContext locDbContext, IUserService userService)
{
this.LocDbContext = locDbContext;
this.UserService = userService;
}
public async Task<ViewModels.DisplayChannel> FindOrDefaultAsync(long id)
{
var currentMemberId = this.UserService.Current.Id;
// do some async EF request …
}
}
// In the controller
[Authorize]
[RoutePrefix("channel")]
public class ChannelController : BaseController
{
public ChannelController()
: this(new ChannelService()
{
}
public ChannelController(ChannelService channelService)
{
this.ChannelService = channelService;
}
// …
[HttpGet, Route("~/api/channels/{id}/messages")]
public async Task<ActionResult> GetMessages(long id)
{
var channel = await this.ChannelService
.FindOrDefaultAsync(id);
return PartialView("_Messages", channel);
}
// …
}
I have the code recently refactored, previously I had to give the user on each call to the service.我最近重构了代码,以前我必须在每次调用服务时给用户。 Now I read this article https://www.trycatchfail.com/2014/04/25/using-httpcontext-safely-after-async-in-asp-net-mvc-applications/ and I'm not sure if my code still works.
现在我读了这篇文章https://www.trycatchfail.com/2014/04/25/using-httpcontext-safely-after-async-in-asp-net-mvc-applications/我不确定我的代码仍然有效。 Has anyone a better approach to handle this?
有没有人有更好的方法来处理这个问题? I don't want to give the user on every request to the service.
我不想向用户提供对服务的每个请求。
As long as your web.config
settings are correct , async
/ await
works perfectly well with HttpContext.Current
.只要您的
web.config
设置正确, async
/ await
可以与HttpContext.Current
完美配合。 I recommend setting httpRuntime
targetFramework
to 4.5
to remove all "quirks mode" behavior.我建议将
httpRuntime
targetFramework
设置为4.5
以删除所有“怪癖模式”行为。
Once that is done, plain async
/ await
will work perfectly well.一旦完成,普通的
async
/ await
将工作得很好。 You'll only run into problems if you're doing work on another thread or if your await
code is incorrect.如果您在另一个线程上工作或者您的
await
代码不正确,您只会遇到问题。
First, the "other thread" problem;一、“其他线程”问题; this is the second problem in the blog post you linked to.
这是您链接到的博客文章中的第二个问题。 Code like this will of course not work correctly:
像这样的代码当然不能正常工作:
async Task FakeAsyncMethod()
{
await Task.Run(() =>
{
var user = _userService.Current;
...
});
}
This problem actually has nothing to do with asynchronous code;这个问题实际上与异步代码无关; it has to do with retrieving a context variable from a (non-request) thread pool thread.
它与从(非请求)线程池线程中检索上下文变量有关。 The exact same problem would occur if you try to do it synchronously.
如果您尝试同步执行,则会出现完全相同的问题。
The core problem is that the asynchronous version is using fake asynchrony.核心问题是异步版本使用了假异步。 This inappropriate, especially on ASP.NET.
这不合适,尤其是在 ASP.NET 上。 The solution is to simply remove the fake-asynchronous code and make it synchronous (or truly asynchronous, if it actually has real asynchronous work to do):
解决方案是简单地删除假异步代码并使其同步(或真正异步,如果它确实有真正的异步工作要做):
void Method()
{
var user = _userService.Current;
...
}
The technique recommended in the linked blog (wrapping the HttpContext
and providing it to the worker thread) is extremely dangerous.链接博客中推荐的技术(包装
HttpContext
并将其提供给工作线程)非常危险。 HttpContext
is designed to be accessed only from one thread at a time and AFAIK is not threadsafe at all. HttpContext
被设计为一次只能从一个线程访问,而 AFAIK 根本不是线程安全的。 So sharing it among different threads is asking for a world of hurt.所以在不同的线程之间共享它是在寻求一个伤害的世界。
If the await
code is incorrect, then it causes a similar problem.如果
await
代码不正确,则会导致类似的问题。 ConfigureAwait(false)
is a technique commonly used in library code to notify the runtime that it doesn't need to return to a specific context. ConfigureAwait(false)
是库代码中常用的一种技术,用于通知运行时它不需要返回到特定上下文。 Consider this code:考虑这个代码:
async Task MyMethodAsync()
{
await Task.Delay(1000).ConfigureAwait(false);
var context = HttpContext.Current;
// Note: "context" is not correct here.
// It could be null; it could be the correct context;
// it could be a context for a different request.
}
In this case, the problem is obvious.在这种情况下,问题很明显。
ConfigureAwait(false)
is telling ASP.NET that the rest of the current method does not need the context, and then it immediately accesses that context. ConfigureAwait(false)
告诉 ASP.NET 当前方法的其余部分不需要上下文,然后它立即访问该上下文。 When you start using context values in your interface implementations, though, the problem is not as obvious:但是,当您开始在接口实现中使用上下文值时,问题就不那么明显了:
async Task MyMethodAsync()
{
await Task.Delay(1000).ConfigureAwait(false);
var user = _userService.Current;
}
This code is just as wrong but not as obviously wrong, since the context is hidden behind an interface.这段代码同样是错误的,但并没有那么明显错误,因为上下文隐藏在接口后面。
So, the general guideline is: use ConfigureAwait(false)
if you know that the method does not depend on its context (directly or indirectly);因此,一般准则是:如果您知道该方法不依赖于其上下文(直接或间接),则使用
ConfigureAwait(false)
); otherwise, do not use ConfigureAwait
.否则,不要使用
ConfigureAwait
。 If it's acceptable in your design to have interface implementations use the context in their implementation, then any method that calls an interface method should not use ConfigureAwait(false)
:如果它在你的设计中可以接受的接口实现在执行使用上下文,然后调用接口方法不应使用任何方法
ConfigureAwait(false)
:
async Task MyMethodAsync()
{
await Task.Delay(1000);
var user = _userService.Current; // works fine
}
As long as you follow that guideline, async
/ await
will work perfectly with HttpContext.Current
.只要您遵循该准则,
async
/ await
将与HttpContext.Current
完美配合。
Async is fine.异步没问题。 The problem is when you post the work to a different thread.
问题是当您将作品发布到不同的线程时。 If your application is setup as 4.5+, the asynchronous callback will be posted in the original context, so you'll also have the proper
HttpContext
etc.如果您的应用程序设置为 4.5+,异步回调将发布在原始上下文中,因此您还将拥有正确的
HttpContext
等。
You don't want to access shared state in a different thread anyway, and with Task
s, you rarely need to handle that explicitly - just make sure you put all your inputs as arguments, and only return a response, rather than reading or writing to a shared state (eg HttpContext
, static fields etc.)无论如何,您不想访问不同线程中的共享状态,并且使用
Task
,您很少需要显式处理 - 只需确保将所有输入作为参数,并且只返回响应,而不是读取或写入到共享状态(例如HttpContext
、静态字段等)
There is no problem, if your ViewModels.DisplayChannel
is a simple object without additional logic.没有问题,如果您的
ViewModels.DisplayChannel
是一个没有附加逻辑的简单对象。
A problem may occur, if the result of your Task
references to "some context objects", fe to HttpContext.Current
.如果您的
Task
的结果引用了“某些上下文对象”,则可能会出现问题,例如HttpContext.Current
。 Such objects are often attached to the thread, but entire code after await
may be executed in another thread.此类对象通常附加到线程,但
await
之后的整个代码可能会在另一个线程中执行。
Keep in mind, that UseTaskFriendlySynchronizationContext
doesn't solve all your problems.请记住,
UseTaskFriendlySynchronizationContext
并不能解决您的所有问题。 If we are talking about ASP.NET MVC, this setting ensures that Controller.HttpContext
contains correct value as before await
as after.如果我们谈论的是 ASP.NET MVC,此设置可确保
Controller.HttpContext
包含正确的值,就像之前一样await
和之后。 But it doesn't ensure that HttpContext.Current
contains correct value, and after await
it still can be null .但它并不能确保
HttpContext.Current
包含正确的值,并且在await
之后它仍然可以为null 。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.