将 HttpContext.Current.User 与异步等待一起使用的正确方法
Posted
技术标签:
【中文标题】将 HttpContext.Current.User 与异步等待一起使用的正确方法【英文标题】:Correct way to use HttpContext.Current.User with async await 【发布时间】:2015-04-10 05:59:10 【问题描述】:我正在处理异步操作并像这样使用 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/,我不确定我的代码是否仍然有效。 有没有人更好的方法来处理这个?我不想在每次请求服务时都向用户提供。
【问题讨论】:
【参考方案1】:只要您的web.config
settings are correct、async
/await
与HttpContext.Current
完美配合。我建议将 httpRuntime
targetFramework
设置为 4.5
以删除所有“怪癖模式”行为。
完成后,普通的async
/await
将运行良好。如果您在另一个线程上工作或者您的await
代码不正确,您只会遇到问题。
首先,“其他线程”问题;这是您链接到的博客文章中的第二个问题。这样的代码当然不能正常工作:
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
完美配合。
【讨论】:
如果您想知道您的 ASP.NET 应用程序在哪个运行时下运行,请打断点并使用静态属性:HttpRuntime.TargetFramework
。【参考方案2】:
异步没问题。问题是当您将工作发布到不同的线程时。如果您的应用程序设置为 4.5+,则异步回调将发布在原始上下文中,因此您还将拥有正确的 HttpContext
等。
无论如何,您都不想在不同的线程中访问共享状态,而使用Task
s,您很少需要显式处理它 - 只需确保将所有输入作为参数,并且只返回响应,而不是读取或写入共享状态(例如HttpContext
、静态字段等)
【讨论】:
【参考方案3】:没有问题,如果你的ViewModels.DisplayChannel
是一个没有额外逻辑的简单对象。
如果您的Task
引用“某些上下文对象”的结果,则可能会出现问题,例如到HttpContext.Current
。此类对象通常附加到线程,但await
之后的整个代码可能会在另一个线程中执行。
请记住,UseTaskFriendlySynchronizationContext
并不能解决您的所有问题。如果我们谈论的是 ASP.NET MVC,此设置可确保 Controller.HttpContext
包含正确的值,就像之前一样 await
和之后一样。但它并不能保证HttpContext.Current
包含正确的值,在await
之后它仍然可以是null。
【讨论】:
如果我正确理解您的答案。那么,如果我在异步 EF 请求后有其他使用自己的 UserService 的服务调用(例如对另一个服务),是否会出现问题 是的。尝试返回普通对象(并避免延迟加载 EF 实体)。 @MarkShevchenko 您能否详细说明在具有正确 web.config 设置的 .NET 4.5.2 应用程序中,在什么条件下 HttpContext.Current 可能为空?我正在解决一个大型遗留 .NET 应用程序中的这个问题,你的评论是我第一次看到暗示null
在等待之后是一个有效状态。以上是关于将 HttpContext.Current.User 与异步等待一起使用的正确方法的主要内容,如果未能解决你的问题,请参考以下文章