RedisSessionProvider和Async方法

时间:2017-08-03 23:07:47

标签: c# azure asynchronous redis

所以,首先是免责声明 - 我是这个异步编程的新手。所以在我开始实现我当前的项目之前,我已经运行了一系列测试来解决问题并更好地理解整个事情。

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

这是我的小测试应用程序:

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为null但Session.SessionID不为null,我怀疑它使用的是另一个SessionState提供程序?关于该主题,重要的是要注意我使用RedisSessionProvider作为自定义SessionState提供程序(对于Azure)。我是否需要使用RedisSessionProvider来允许它处理异步调用?有人可以指出我正确的方向,也许可以解释一下SessionState如何处理异步调用?

TIA

1 个答案:

答案 0 :(得分:1)

无法同时跨多个线程共享会话。这是所有ASP.NET会话的限制,而不仅仅是Redis会话。

核心问题是使用Task.RunTask.Run将使ASP.NET在每个请求中使用多个线程,这是一个非常糟糕的主意,因为它会严重影响您的可伸缩性。 Task.Run也明确地在请求上下文之外执行,因此HttpContext.Current将为null - 这是设计使然。

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

// 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);
  ...
}

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

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

如果你绝对必须在服务器上执行并行代码(再次,我真的不能推荐这个),那么你需要提取你需要的所有数据在开始并行工作之前的会话。 E.g:

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