我正在处理异步操作并使用像这样的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);
}
// …
}
我有最近重构的代码,以前我必须在每次调用服务时给用户。 现在我读了这篇文章http://trycatchfail.com/blog/post/Using-HttpContext-Safely-After-Async-in-ASPNET-MVC-Applications.aspx,我不确定我的代码是否仍然有效。 有谁有更好的方法来处理这个?我不想向用户提供服务的每个请求。
答案 0 :(得分:52)
只要您的web.config
settings are correct,async
/ await
与HttpContext.Current
完美配合即可。我建议将httpRuntime
targetFramework
设置为4.5
以删除所有&#34; quirks模式&#34;行为。
完成后,普通async
/ await
将非常有效。如果您正在其他线程上工作或await
代码不正确,您只会遇到问题。
首先,&#34;其他线程&#34;问题;这是您链接到的博客文章中的第二个问题。像这样的代码当然不能正常工作:
async Task FakeAsyncMethod()
{
await Task.Run(() =>
{
var user = _userService.Current;
...
});
}
这个问题实际上与异步代码无关;它与从(非请求)线程池线程中检索上下文变量有关。如果您尝试同步执行此操作,则会出现完全相同的问题。
核心问题是异步版本正在使用 fake 异步。这不合适,特别是在ASP.NET上。解决方案是简单地删除伪异步代码并使其同步(或者真正异步,如果它实际上有真正的异步工作):
void Method()
{
var user = _userService.Current;
...
}
链接博客中推荐的技术(包装HttpContext
并将其提供给工作线程)非常危险。 HttpContext
旨在一次只能从一个线程访问,而AFAIK根本不是线程安全的。因此,在不同的线程之间分享它是一个受伤的世界。
如果await
代码不正确,则会导致类似的问题。 ConfigureAwait(false)
是库代码中常用的一种技术,用于通知运行时它不需要返回特定的上下文。请考虑以下代码:
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.
}
在这种情况下,问题很明显。 ConfigureAwait(false)
告诉ASP.NET当前方法的其余部分不需要上下文,然后它立即访问该上下文。但是,当您在接口实现中开始使用上下文值时,问题就不那么明显了:
async Task MyMethodAsync()
{
await Task.Delay(1000).ConfigureAwait(false);
var user = _userService.Current;
}
这段代码同样错误但不明显错误,因为上下文隐藏在接口后面。
因此,一般准则是:使用ConfigureAwait(false)
如果您知道该方法不依赖于其上下文(直接或间接);否则,请勿使用ConfigureAwait
。如果您的设计中接受实现接口实现在其实现中使用上下文,那么调用接口方法的任何方法都应该不使用ConfigureAwait(false)
:
async Task MyMethodAsync()
{
await Task.Delay(1000);
var user = _userService.Current; // works fine
}
只要您遵循该指南,async
/ await
将与HttpContext.Current
完美配合。
答案 1 :(得分:2)
异步很好。问题是当您将工作发布到其他线程时。如果您的应用程序设置为4.5+,则异步回调将在原始上下文中发布,因此您还将拥有正确的HttpContext
等。
您不希望在不同的线程中访问共享状态,而使用Task
,您很少需要明确处理 - 只需确保将所有输入作为参数,并且只返回响应,而不是读取或写入共享状态(例如HttpContext
,静态字段等。)
答案 2 :(得分:2)
如果您的ViewModels.DisplayChannel
是一个没有额外逻辑的简单对象,则没有问题。
如果Task
的结果引用了某些上下文对象&#34;,f.e,则可能会出现问题。到HttpContext.Current
。这些对象通常附加到线程,但await
之后的整个代码可以在另一个线程中执行。
请注意,UseTaskFriendlySynchronizationContext
并未解决您的所有问题。如果我们讨论的是ASP.NET MVC,则此设置可确保Controller.HttpContext
包含与await
之前一样的正确值。但它并不能确保HttpContext.Current
包含正确的值,而await
之后仍然可以 null 。