简体   繁体   English

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

[英]Correct way to use HttpContext.Current.User with async await

I am working with async actions and use the HttpContext.Current.User like this我正在处理异步操作并像这样使用 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);
    }

    // …
}

I have the code recently refactored, previously I had to give the user on each call to the service.我最近重构了代码,以前我必须在每次调用服务时给用户。 Now I read this article https://www.trycatchfail.com/2014/04/25/using-httpcontext-safely-after-async-in-asp-net-mvc-applications/ and I'm not sure if my code still works.现在我读了这篇文章https://www.trycatchfail.com/2014/04/25/using-httpcontext-safely-after-async-in-asp-net-mvc-applications/我不确定我的代码仍然有效。 Has anyone a better approach to handle this?有没有人有更好的方法来处理这个问题? I don't want to give the user on every request to the service.我不想向用户提供对服务的每个请求。

As long as your web.config settings are correct , async / await works perfectly well with HttpContext.Current .只要您的web.config设置正确async / await可以与HttpContext.Current完美配合。 I recommend setting httpRuntime targetFramework to 4.5 to remove all "quirks mode" behavior.我建议将httpRuntime targetFramework设置为4.5以删除所有“怪癖模式”行为。

Once that is done, plain async / await will work perfectly well.一旦完成,普通的async / await将工作得很好。 You'll only run into problems if you're doing work on another thread or if your await code is incorrect.如果您在另一个线程上工作或者您的await代码不正确,您只会遇到问题。


First, the "other thread" problem;一、“其他线程”问题; this is the second problem in the blog post you linked to.这是您链接到的博客文章中的第二个问题。 Code like this will of course not work correctly:像这样的代码当然不能正常工作:

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

This problem actually has nothing to do with asynchronous code;这个问题实际上与异步代码无关; it has to do with retrieving a context variable from a (non-request) thread pool thread.它与从(非请求)线程池线程中检索上下文变量有关。 The exact same problem would occur if you try to do it synchronously.如果您尝试同步执行,则会出现完全相同的问题。

The core problem is that the asynchronous version is using fake asynchrony.核心问题是异步版本使用了异步。 This inappropriate, especially on ASP.NET.这不合适,尤其是在 ASP.NET 上。 The solution is to simply remove the fake-asynchronous code and make it synchronous (or truly asynchronous, if it actually has real asynchronous work to do):解决方案是简单地删除假异步代码并使其同步(或真正异步,如果它确实有真正的异步工作要做):

void Method()
{
  var user = _userService.Current;
  ...
}

The technique recommended in the linked blog (wrapping the HttpContext and providing it to the worker thread) is extremely dangerous.链接博客中推荐的技术(包装HttpContext并将其提供给工作线程)非常危险。 HttpContext is designed to be accessed only from one thread at a time and AFAIK is not threadsafe at all. HttpContext被设计为一次只能从一个线程访问,而 AFAIK 根本不是线程安全的。 So sharing it among different threads is asking for a world of hurt.所以在不同的线程之间共享它是在寻求一个伤害的世界。


If the await code is incorrect, then it causes a similar problem.如果await代码不正确,则会导致类似的问题。 ConfigureAwait(false) is a technique commonly used in library code to notify the runtime that it doesn't need to return to a specific context. ConfigureAwait(false)是库代码中常用的一种技术,用于通知运行时它不需要返回到特定上下文。 Consider this code:考虑这个代码:

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.
}

In this case, the problem is obvious.在这种情况下,问题很明显。 ConfigureAwait(false) is telling ASP.NET that the rest of the current method does not need the context, and then it immediately accesses that context. ConfigureAwait(false)告诉 ASP.NET 当前方法的其余部分不需要上下文,然后它立即访问该上下文。 When you start using context values in your interface implementations, though, the problem is not as obvious:但是,当您开始在接口实现中使用上下文值时,问题就不那么明显了:

async Task MyMethodAsync()
{
  await Task.Delay(1000).ConfigureAwait(false);
  var user = _userService.Current;
}

This code is just as wrong but not as obviously wrong, since the context is hidden behind an interface.这段代码同样是错误的,但并没有那么明显错误,因为上下文隐藏在接口后面。

So, the general guideline is: use ConfigureAwait(false) if you know that the method does not depend on its context (directly or indirectly);因此,一般准则是:如果您知道该方法不依赖于其上下文(直接或间接),使用ConfigureAwait(false) ); otherwise, do not use ConfigureAwait .否则,不要使用ConfigureAwait If it's acceptable in your design to have interface implementations use the context in their implementation, then any method that calls an interface method should not use ConfigureAwait(false) :如果它在你的设计中可以接受的接口实现在执行使用上下文,然后调用接口方法不应使用任何方法ConfigureAwait(false)

async Task MyMethodAsync()
{
  await Task.Delay(1000);
  var user = _userService.Current; // works fine
}

As long as you follow that guideline, async / await will work perfectly with HttpContext.Current .只要您遵循该准则, async / await将与HttpContext.Current完美配合。

Async is fine.异步没问题。 The problem is when you post the work to a different thread.问题是当您将作品发布到不同的线程时。 If your application is setup as 4.5+, the asynchronous callback will be posted in the original context, so you'll also have the proper HttpContext etc.如果您的应用程序设置为 4.5+,异步回调将发布在原始上下文中,因此您还将拥有正确的HttpContext等。

You don't want to access shared state in a different thread anyway, and with Task s, you rarely need to handle that explicitly - just make sure you put all your inputs as arguments, and only return a response, rather than reading or writing to a shared state (eg HttpContext , static fields etc.)无论如何,您不想访问不同线程中的共享状态,并且使用Task ,您很少需要显式处理 - 只需确保将所有输入作为参数,并且只返回响应,而不是读取或写入到共享状态(例如HttpContext 、静态字段等)

There is no problem, if your ViewModels.DisplayChannel is a simple object without additional logic.没有问题,如果您的ViewModels.DisplayChannel是一个没有附加逻辑的简单对象。

A problem may occur, if the result of your Task references to "some context objects", fe to HttpContext.Current .如果您的Task的结果引用了“某些上下文对象”,则可能会出现问题,例如HttpContext.Current Such objects are often attached to the thread, but entire code after await may be executed in another thread.此类对象通常附加到线程,但await之后的整个代码可能会在另一个线程中执行。

Keep in mind, that UseTaskFriendlySynchronizationContext doesn't solve all your problems.请记住, UseTaskFriendlySynchronizationContext并不能解决您的所有问题。 If we are talking about ASP.NET MVC, this setting ensures that Controller.HttpContext contains correct value as before await as after.如果我们谈论的是 ASP.NET MVC,此设置可确保Controller.HttpContext包含正确的值,就像之前一样await和之后。 But it doesn't ensure that HttpContext.Current contains correct value, and after await it still can be null .但它并不能确保HttpContext.Current包含正确的值,并且在await之后它仍然可以为null

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

相关问题 ASP.Net MVC 5-在控制器外部访问HttpContext.Current.User的正确方法? - ASP.Net MVC 5 - Correct way to access HttpContext.Current.User outside of controller? HttpContext.Current.User对于无状态会话是否安全? - Is HttpContext.Current.User Safe for Stateless Session? HttpContext.Current.User和HttpContext.User有什么区别 - What is the difference between HttpContext.Current.User and HttpContext.User HttpContext.Current.User 和 this.User.Identity 的区别 - Difference between HttpContext.Current.User and this.User.Identity HttpContext.Current.User未填充启用Windows身份验证 - HttpContext.Current.User not populated with Windows Authentication enabled 角色添加到 HttpContext.current.user 但 isInRole(rolename) 不起作用 - Role added to HttpContext.current.user but isInRole(rolename) is not working 从Thread.CurrentPrincipal设置HttpContext.Current.User - Set HttpContext.Current.User from Thread.CurrentPrincipal IsOneWay = true时HttpContext.Current.User为null - HttpContext.Current.User is null when IsOneWay = true 安装.NET Framework 4.5后,HttpContext.Current.User为null - HttpContext.Current.User is null after installing .NET Framework 4.5 设置HttpContext.Current.User引发null引用异常 - setting HttpContext.Current.User throws null reference exception
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM