1

I am investigating async and await in the context of ASP.NET MVC controller methods, and am getting some unexpected behavior.

I have the following controller class:

[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();
    }
}

And simple views for each method Check1 and Check2:

Check1

@{
    ViewBag.Title = "Check1";
}

<h2>View with Delay</h2>

Check2

@{
    ViewBag.Title = "Check2";
    var check = ViewBag.Test;
}

<h2>Immediate View @check</h2>

Now immediately after starting the application, when I open http://localhost:23131/Home/Check1 in one tab and http://localhost:23131/Home/Check2 in 2nd tab, the call to Check1 returns after 20 seconds as expected, and call to Check2 returns immediately, but it doesn't have the value set in the session state, which I fail to understand why. It should be set immediately since it is before the delay. However, correct value is printed on output window.

Now after Check1 returns, hitting refresh on Check2 tab brings value from session in viewbag and displays it.

After this, if I again refresh both tabs, Check2 doesn't return after Check1 is completed (20 seconds delay) despite Check2 being async.

My questions are:

  1. First time when both tabs are opened, Check2 returns immediately due to Check1 being async, and it is not blocked despite SessionStateBehavior.Required since await statement returns control until awaited task completes. Why doesn't Check2 gets the value before Check1 returns?
  2. After 1st time, Check2 gets stuck until Check1 returns despite Check1 being async, why is this? Upon re-running the application, Check1 returns immediately as stated in the previous question but only once.
  3. Is there any concept I have missed or stated wrong in my explanation.

Using ASP.NET with .NET Framework 4.5, Visual Studio 2013 running on Windows 7 Ultimate.

SpeedBirdNine
  • 4,610
  • 11
  • 49
  • 67

1 Answers1

6

For your question 2, if I understand correctly what you are doing is you refresh both tabs (presumably the one with check1 just before check2), and observe that check2 is not finishing loading until after check1 is finished.

The reason for this is that asp.net processes requests that are (potentially) writing to the same session in serial, not in parallel. That means, as long as any one request is pending, a second request will not even start processing until the first request is finished. Using tasks, async or manual thread handling will not work around this behaviour. The reason is that accessing the session is not a thread-safe operation. Potential workarounds are to not use session state in pages that do not require them, or use ReadOnly session state in pages that do not need to write to the session.

Both are accomplished by using the EnableSessionState property on the page, either setting to False to disable session state, or to ReadOnly to indicate that you do not write to the session in any requests to this page.

Also see for example answers here.

For question 1, it may be the reason is that session state is persisted only after the ReleaseRequestState life-cycle event (which is executed at the very end of the request), even though I would have expected this to to only matter when using a non-inproc session store. The more likely reason is however, that the request to check2 executes first (and blocks even the start of processing for check1). Perhaps you can elaborate how exactly you test this and how you make sure which request is executed first.

Edit:

what may be happening is this: in your first try, you do not actually use the same session (the session is not yet created when you start and a new one is created for each request. That also explains why the second request is not blocked until the first is finished). Then, on the second try, both will use the same session (because the last written session-cookie is used by both tabs). You can confirm that theory by printing the asp session (Session.SessionID) id and/or using cookieless session state

Community
  • 1
  • 1
Ben Schwehn
  • 4,505
  • 1
  • 27
  • 45
  • Thanks for reply, in all cases I executed check1 before check2. – SpeedBirdNine Sep 16 '14 at 14:16
  • 2
    in this case, what may be happening is this: in your first try, you do not actually use the same session (the session is not yet created when you start and a new one is created for each request. That also explains why the second request is not blocked until the first is finished). Then, on the second try, both will use the same session (because the last written session-cookie is used by both tabs). You can confirm that theory by printing the asp session (Session.SessionID) id and/or using cookieless session state. – Ben Schwehn Sep 16 '14 at 14:25
  • Yes, it is confirmed, in the first try both sessions have different sessionID and in the second case both have same sessionID. After first request completes and session cookie is set, both requests use the same session. And it has nothing to do with `async` at all. Removing async has the same behavior – SpeedBirdNine Sep 17 '14 at 07:37
  • Thanks for the detailed answer and comments. I have accepted your answer but please add the last comment in your answer as it is the answer, and if there is a link to official documentation explaining this it will be helpful. – SpeedBirdNine Sep 17 '14 at 07:39