【问题标题】:RedisSessionProvider and Async methodsRedisSessionProvider 和 Async 方法
【发布时间】:2017-08-03 23:07:46
【问题描述】:

所以,首先声明一下——我是这个异步编程的新手。因此,在我开始实施我当前的项目之前,我一直在运行一系列测试以消除问题并更好地了解整个事情。

我今天遇到了一个与 HttpContext.Current.Session 为 null 相关的问题,这恰好遍布我当前的代码库。

这是我的小测试应用:

public partial class RandomTests : System.Web.UI.Page
{
    protected async void Page_Load(object sender, EventArgs e)
    {
        Debug.WriteLine("----------------------------------------------------------------------------------");
        Debug.WriteLine("Blend dual call start");
        Debug.WriteLine("Operation complete. Returned: " + await BlendDualCall() + " at " + DateTime.Now.ToString("mm':'ss':'fff"));
        Debug.WriteLine("Blend dual call complete");
        Debug.WriteLine("----------------------------------------------------------------------------------");
    }

    public async Task<string> BlendDualCall()
    {
        var cTask = Task.Run(() => YLogin(1000));
        var fTask = Task.Run(() => FLogin(2000));

        Debug.WriteLine("Awaiting results.");
        var yResult = await cTask;
        Debug.WriteLine("YLogin result returned at " + DateTime.Now.ToString("mm':'ss':'fff"));
        var fResult = await fTask;
        Debug.WriteLine("FLogin result returned at " + DateTime.Now.ToString("mm':'ss':'fff"));

        return yResult + "   " + fResult;
    }

    #region Non-Async Methods

    public string FLogin(int waitTime)
    {
        Debug.WriteLine("FLogin process executed on thread id: " + Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(waitTime);

        //Session is null here
        return HttpContext.Current.Session.SessionID + "_" + Thread.CurrentThread.ManagedThreadId; 
    }

    public string YLogin(int waitTime)
    {
        Debug.WriteLine("YLogin process executed on thread id: " + Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(waitTime);

        //Session is not null here (System.Web.SessionState.HttpSessionState). Whats the difference?
        return Session.SessionID + "_" + Thread.CurrentThread.ManagedThreadId;
    }

    #endregion
}

web.config 中的自定义 SessionState 提供程序:

<sessionState mode="Custom" customProvider="RedisSessionProvider">
  <providers>
    <add name="RedisSessionProvider" type="Microsoft.Web.Redis.RedisSessionStateProvider" host="******" port="***" accessKey="********" ssl="true" />
  </providers>
</sessionState>

我正在寻找一些关于 SessionState 发生了什么以及为什么它在异步调用中为空的理解。有趣的是 HttpContext.Current.Session.SessionID 为空但 Session.SessionID 不为空,我怀疑它使用的是不同的 SessionState 提供程序?关于该主题,重要的是要注意我使用 RedisSessionProvider 作为自定义 SessionState 提供程序(用于 Azure)。我需要对 RedisSessionProvider 做些什么来让它处理异步调用吗?有人能指出我正确的方向吗?也许可以解释一下 SessionState 如何处理异步调用?

TIA

【问题讨论】:

    标签: c# azure asynchronous redis


    【解决方案1】:

    会话不能同时在多个线程之间共享。这是所有 ASP.NET 会话的限制,而不仅仅是 Redis 会话。

    核心问题是Task.Run的使用。 Task.Run 将使 ASP.NET 每个请求使用多个线程,这是一个非常糟糕的主意,因为它严重影响了您的可伸缩性。 Task.Run 也显式地跳出请求上下文,因此 HttpContext.Current 将是 null - 这是设计使然。

    最好的解决方案是删除所有对Task.Run 的调用。您可以很好地执行多个并发异步操作 - 如果它们实际上是异步的(不仅仅是在后台线程上运行)。例如:

    // True asynchronous calls
    public async Task<string> FLogin(int waitTime)
    {
      await Task.Delay(waitTime);
      return HttpContext.Current.Session.SessionID + "_" + Thread.CurrentThread.ManagedThreadId; 
    }
    
    public async Task<string> BlendDualCall()
    {
      var cTask = YLogin(1000);
      var fTask = FLogin(2000);
      ...
    }
    

    如果它们不是真正异步的,那么下一个最佳解决方案是一次执行一个。例如:

    public string FLogin(int waitTime);
    public string BlendDualCall()
    {
      var yResult = YLogin(1000);
      var fResult = FLogin(2000);
      ...
    }
    

    如果您绝对必须在服务器上执行并行代码(同样,我真的不推荐这样做),那么您需要在开始之前从会话中提取所有需要的数据并行工作。例如:

    public string FLogin(int waitTime, string sessionId)
    {
      Thread.Sleep(waitTime);
      return sessionID + "_" + Thread.CurrentThread.ManagedThreadId; 
    }
    public string BlendDualCall()
    {
      var sessionId = HttpContext.Current.Session.SessionID.ToString();
      var cTask = Task.Run(() => YLogin(1000, sessionId));
      var fTask = Task.Run(() => FLogin(2000, sessionId));
      ...
    }
    

    【讨论】:

    • 好的,谢谢你的回复,我开始明白一些了。所以在我看来,最好的做法是将我的大部分代码转换为真正的异步方法。也许是一个愚蠢的问题,但是在做这样的事情时,在使用 await 调用时我应该记住什么?如果方法中没有任何应该或可以等待的东西怎么办?这就是大家津津乐道的僵尸代码吗?我是否需要转换我正在转换的方法中调用的每个方法?
    • 我建议从“叶子”开始。也就是说,执行 I/O 的代码应该转换为在最低级别使用 await - 发送 HTTP 请求、查询数据库等。然后让 async 从那里生长出来。
    • 感谢您的宝贵时间。我真的很感激。
    猜你喜欢
    • 2018-11-09
    • 1970-01-01
    • 1970-01-01
    • 2020-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-28
    • 2016-10-10
    相关资源
    最近更新 更多