9

In the following example, I am setting a value to an AsyncLocal<string> variable on my HttpApplication subclass (i.e. Global.asax) from within Application_BeginRequest():

public class Global : System.Web.HttpApplication
{
    public static AsyncLocal<string> AsyncLocalState = new AsyncLocal<string>();

    protected void Application_BeginRequest(object sender, EventArgs e)
    {
        AsyncLocalState.Value = HttpContext.Current.Request.Path;
    }

    protected void Application_AuthenticateRequest(object sender, EventArgs e)
    {
        var path = AsyncLocalState.Value;
    }

    protected void Application_EndRequest(object sender, EventArgs e)
    {
        var path = AsyncLocalState.Value;
    }
}

Later on, I will attempt to access the value of this AsyncLocal variable from within a handler, such as an MVC action method, or even just a plain IHttpHandler.

If I send a large enough request (e.g. a POST with more than 15KB of data -- the larger the request, the easier it is to observe), there is a very good chance that the value of AsyncLocalState is NULL when accessed from a handler even though it was set on BeginRequest.

This is reproducible from a brand-new ASP.NET project without any other libraries/modules/handlers loaded.

Is this a bug? Or maybe I'm doing something wrong? Or is ASP.NET just too unstable for this?

Addition note: the exact same behavior is observed if I instead use CallContext.LogicalGetData/CallContext.LogicalSetData.

Platform: ASP.NET, .NET 4.6.2, on Windows 7

Update: After trying to dig around, I've found a lot of references to, but nothing authoritatively saying that the ExecutionContext does not flow between ASP.NET pipeline events (except when it does?). And both AsyncLocal and the logical call context are based on the ExecutionContext.

Joseph Daigle
  • 47,650
  • 10
  • 49
  • 73
  • Are you sure / can you check that the `BeginRequest` handler and the later method are both in fact [in the same async context](http://stackoverflow.com/q/35802014/11683)? – GSerg Apr 13 '17 at 11:57
  • @GSerg I'm not exactly sure how to check that. I hooked up a `ValueChangedHandler` to the `AsyncLocal` and it doesn't get called after initially being set. It's also just weird how to depends so much on the size of the request - small requests consistently "work" and large requests do not consistently "work". – Joseph Daigle Apr 13 '17 at 12:23
  • You check that by making sure `SynchronizationContext.Current` returns the same object from the event handler and from the method. `ValueChangedHandler` should not fire because the value does not change, it's just that can't be seen from different contexts like a `ThreadLocal` value cannot be seen from different threads. – GSerg Apr 13 '17 at 12:28
  • It also doesn't seem very weird that small requests get through; that is often an indication of a race condition. Neither small nor big requests work reliably, but small ones are very likely to have time to get through. – GSerg Apr 13 '17 at 12:30
  • @GSerg It seems that the `SynchronizationContext.Current` instance is the same even when the AsyncLocal fails to flow. I tested it by setting it to a regular static variable in `BeginRequest` and then comparing in the handler. – Joseph Daigle Apr 13 '17 at 12:44
  • 1
    @GSerg regarding the request size; you're onto something about the timing. If I break inside of `BeginRequest` after setting the `AsyncLocal` value, and pause for a few moments (or even just `Thread.Sleep(2000)`), then it works more often. – Joseph Daigle Apr 13 '17 at 12:48

1 Answers1

12

The closest thing to an authoritative answer is this comment by David Fowl on GitHub.

The ExecutionContext does not flow between ASP.NET pipeline events if these events do not execute synchronously. Therefore, don't use AsyncLocal or the logical CallContext to persist state; use HttpContext.Items.

Update: .NET 4.7.1 adds a new callback method, HttpApplication.OnExecuteRequestStep, which per documentation "provides extensibility to the ASP.NET pipeline to make it easy for developers to implement features in ambient context scenarios and build libraries that care about ASP.NET execution flow (for example, tracing, profiling, diagnostics, and transactions)."

This is precisely what someone would need in order to restore the AsyncLocal state or the logical CallContext between ASP.NET pipeline events.

Joseph Daigle
  • 47,650
  • 10
  • 49
  • 73
  • Can you explain how the CallContext could be restored using the `OnExecuteRequestStep`? – Jules Jan 16 '23 at 11:26