繁体   English   中英

在异步等待中使用 HttpContext.Current.User 的正确方法

[英]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/我不确定我的代码仍然有效。 有没有人有更好的方法来处理这个问题? 我不想向用户提供对服务的每个请求。

只要您的web.config设置正确async / await可以与HttpContext.Current完美配合。 我建议将httpRuntime targetFramework设置为4.5以删除所有“怪癖模式”行为。

一旦完成,普通的async / await将工作得很好。 如果您在另一个线程上工作或者您的await代码不正确,您只会遇到问题。


一、“其他线程”问题; 这是您链接到的博客文章中的第二个问题。 像这样的代码当然不能正常工作:

async Task FakeAsyncMethod()
{
  await Task.Run(() =>
  {
    var user = _userService.Current;
    ...
  });
}

这个问题实际上与异步代码无关; 它与从(非请求)线程池线程中检索上下文变量有关。 如果您尝试同步执行,则会出现完全相同的问题。

核心问题是异步版本使用了异步。 这不合适,尤其是在 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完美配合。

异步没问题。 问题是当您将作品发布到不同的线程时。 如果您的应用程序设置为 4.5+,异步回调将发布在原始上下文中,因此您还将拥有正确的HttpContext等。

无论如何,您不想访问不同线程中的共享状态,并且使用Task ,您很少需要显式处理 - 只需确保将所有输入作为参数,并且只返回响应,而不是读取或写入到共享状态(例如HttpContext 、静态字段等)

没有问题,如果您的ViewModels.DisplayChannel是一个没有附加逻辑的简单对象。

如果您的Task的结果引用了“某些上下文对象”,则可能会出现问题,例如HttpContext.Current 此类对象通常附加到线程,但await之后的整个代码可能会在另一个线程中执行。

请记住, UseTaskFriendlySynchronizationContext并不能解决您的所有问题。 如果我们谈论的是 ASP.NET MVC,此设置可确保Controller.HttpContext包含正确的值,就像之前一样await和之后。 但它并不能确保HttpContext.Current包含正确的值,并且在await之后它仍然可以为null

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM