使用 HttpContext.Current.User 和 async await 的正确方法
Correct way to use HttpContext.Current.User with async await
我正在使用异步操作并像这样使用 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);
}
// …
}
我最近重构了代码,以前我必须在每次调用该服务时向用户提供。
现在我阅读了这篇文章 https://www.trycatchfail.com/2014/04/25/using-httpcontext-safely-after-async-in-asp-net-mvc-applications/,我不确定我的代码是否仍然有效。
有没有人有更好的方法来处理这个问题?我不想在每次请求服务时都给用户。
异步没问题。问题是当您 post 将工作转移到另一个线程时。如果您的应用程序设置为 4.5+,异步回调将在原始上下文中 posted,因此您还将拥有适当的 HttpContext
等
无论如何你都不想在不同的线程中访问共享状态,并且使用 Task
s,你很少需要明确地处理它 - 只要确保你把所有的输入都作为参数,并且只return 响应,而不是读取或写入共享状态(例如 HttpContext
、静态字段等)
没有问题,如果您的 ViewModels.DisplayChannel
是一个没有附加逻辑的简单对象。
如果您的 Task
的结果引用 "some context objects"、f.e,则可能会出现问题。至 HttpContext.Current
。此类对象通常附加到线程,但 await
之后的整个代码可能会在另一个线程中执行。
请记住,UseTaskFriendlySynchronizationContext
并不能解决您所有的问题。如果我们谈论的是 ASP.NET MVC,此设置可确保 Controller.HttpContext
包含与之前一样的正确值 await
与之后一样。但不保证HttpContext.Current
包含正确的值,await
后仍然可以是null.
只要您的 web.config
settings are correct、async
/await
与 HttpContext.Current
配合得很好。我建议将 httpRuntime
targetFramework
设置为 4.5
以删除所有 "quirks mode" 行为。
完成后,普通 async
/await
将完美运行。如果您在另一个线程上工作或者您的 await
代码不正确,您只会 运行 遇到问题。
首先,"other thread"问题;这是您链接到的博客 post 中的第二个问题。这样的代码当然不能正常工作:
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)
是库代码中常用的一种技术,用于通知 运行 时间它不需要 return 特定上下文。考虑这段代码:
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
.
完美配合
我正在使用异步操作并像这样使用 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);
}
// …
}
我最近重构了代码,以前我必须在每次调用该服务时向用户提供。 现在我阅读了这篇文章 https://www.trycatchfail.com/2014/04/25/using-httpcontext-safely-after-async-in-asp-net-mvc-applications/,我不确定我的代码是否仍然有效。 有没有人有更好的方法来处理这个问题?我不想在每次请求服务时都给用户。
异步没问题。问题是当您 post 将工作转移到另一个线程时。如果您的应用程序设置为 4.5+,异步回调将在原始上下文中 posted,因此您还将拥有适当的 HttpContext
等
无论如何你都不想在不同的线程中访问共享状态,并且使用 Task
s,你很少需要明确地处理它 - 只要确保你把所有的输入都作为参数,并且只return 响应,而不是读取或写入共享状态(例如 HttpContext
、静态字段等)
没有问题,如果您的 ViewModels.DisplayChannel
是一个没有附加逻辑的简单对象。
如果您的 Task
的结果引用 "some context objects"、f.e,则可能会出现问题。至 HttpContext.Current
。此类对象通常附加到线程,但 await
之后的整个代码可能会在另一个线程中执行。
请记住,UseTaskFriendlySynchronizationContext
并不能解决您所有的问题。如果我们谈论的是 ASP.NET MVC,此设置可确保 Controller.HttpContext
包含与之前一样的正确值 await
与之后一样。但不保证HttpContext.Current
包含正确的值,await
后仍然可以是null.
只要您的 web.config
settings are correct、async
/await
与 HttpContext.Current
配合得很好。我建议将 httpRuntime
targetFramework
设置为 4.5
以删除所有 "quirks mode" 行为。
完成后,普通 async
/await
将完美运行。如果您在另一个线程上工作或者您的 await
代码不正确,您只会 运行 遇到问题。
首先,"other thread"问题;这是您链接到的博客 post 中的第二个问题。这样的代码当然不能正常工作:
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)
是库代码中常用的一种技术,用于通知 运行 时间它不需要 return 特定上下文。考虑这段代码:
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
.