簡體   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