将 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/awaitHttpContext.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 等。

无论如何,您都不想在不同的线程中访问共享状态,而使用Tasks,您很少需要显式处理它 - 只需确保将所有输入作为参数,并且只返回响应,而不是读取或写入共享状态(例如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 与异步等待一起使用的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

在剃刀中获取当前用户名

将自己的博客园,打造成个人知乎

如何将thinkcmf导入eclipse

如何将Ios文件上传到

Javascript 将正则表达式 \\n 替换为 \n,将 \\t 替换为 \t,将 \\r 替换为 \r 等等

如何将视频文件转换格式