异步的会话状态意外行为



我正在ASP.NET MVC控制器方法的上下文中研究asyncawait,并得到一些意外的行为。

我有以下控制器类:

[SessionState(System.Web.SessionState.SessionStateBehavior.Required)]
public class HomeController : Controller
{
    // GET: Home
    public ActionResult Index()
    {
        return View();
    }
    public async Task<ActionResult> Check1()
    {
        Session["Test"] = "Check";
        System.Diagnostics.Debug.WriteLine("Session: " + Session["Test"]);
        await Task.Delay(20000);
        return View();
    }
    public ActionResult Check2()
    {
        var test = Session["Test"];
        ViewBag.Test = test;
        return View();
    }
}

每个方法的简单视图Check1和Check2:

检查1

@{
    ViewBag.Title = "Check1";
}
<h2>View with Delay</h2>

检查2

@{
    ViewBag.Title = "Check2";
    var check = ViewBag.Test;
}
<h2>Immediate View @check</h2>

现在,在启动应用程序后,当我在一个选项卡中打开http://localhost:23131/Home/Check1,在第二个选项卡上打开http://localhost:23131/Home/Check2时,对Check1的调用在20秒后按预期返回,对Check2的调用立即返回,但它在会话状态下没有设置值,我不明白为什么。它应该立即设置,因为它在延迟之前。但是,正确的值会打印在输出窗口上。

现在,在Check1返回后,点击Check2选项卡上的刷新会带来viewbag中会话的值并显示它

之后,如果我再次刷新两个选项卡,则在Check1完成后(延迟20秒),Check2不会返回,尽管Check2是async

我的问题是:

  1. 第一次打开两个选项卡时,由于Check1asyncCheck2立即返回,并且由于await语句返回控制直到等待的任务完成,因此尽管SessionStateBehavior.Required,它也不会被阻止。为什么Check2Check1返回之前没有得到值
  2. 第一次之后,Check2被卡住,直到Check1返回,尽管Check1async,为什么?重新运行应用程序后,Check1会立即返回,如前一个问题所述,但仅返回一次
  3. 我的解释中有没有遗漏或说错的概念

使用ASP.NET和.NET Framework 4.5,Visual Studio 2013在Windows 7 Ultimate上运行。

对于您的问题2,如果我正确理解您正在做的是刷新两个选项卡(可能是在check2之前带有check1的选项卡),并观察到check2直到check1完成后才完成加载。

原因是asp.net处理(可能)以串行而非并行方式写入同一会话的请求。这意味着,只要有任何一个请求挂起,第二个请求甚至不会开始处理,直到第一个请求完成。使用任务、异步或手动线程处理将无法解决这种行为。原因是访问会话不是线程安全操作。潜在的解决方法是在不需要会话状态的页面中不使用会话状态,或者在不需要写入会话的页面中使用ReadOnly会话状态。

两者都是通过使用页面上的EnableSessionState属性来实现的,或者设置为False以禁用会话状态,或者设置成ReadOnly以指示在对此页面的任何请求中都不写入会话。

另请参阅此处的示例答案。

对于问题1,原因可能是会话状态仅在ReleaseRequestState生命周期事件(在请求的最后执行)之后才保持,尽管我本以为这只在使用非进程会话存储时才重要。然而,更可能的原因是,check2的请求首先执行(甚至阻止了check1的处理开始)。也许您可以详细说明如何测试它,以及如何确保哪个请求首先执行。

编辑:

可能发生的情况是:在你的第一次尝试中,你实际上并没有使用同一个会话(当你开始时,会话还没有创建,每个请求都会创建一个新的会话。这也解释了为什么在第一个请求完成之前,第二个请求不会被阻止)。然后,在第二次尝试时,两个选项卡都将使用相同的会话(因为最后写入的会话cookie由两个选项卡使用)。您可以通过打印asp会话(session.SessionID)id和/或使用无Cookie会话状态来确认这一理论

最新更新