Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why doesn't an AsyncLocal flow from OWIN middleware to a WebForms page?

Find the full sample app demonstrating this problem here.

I'm trying to use OWIN middleware to populate a static identifier that will be accessible by a WebForms page, but I'm noticing that with certain types this does not behave intuitively - specifically AsyncLocal<T>.

Consider the following working sample, using an int:

Simple static container

public static class Container
{
    public static int CallId { get; set; }
}

Simple OWIN configuration

[assembly: OwinStartup(typeof(Startup))]

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Use((context, next) =>
        {
            Container.CallId = 5;

            return next.Invoke();
        });
    }
}

Simple Default.aspx.cs

protected void Page_Load(object sender, EventArgs e)
{
    if (Container.CallId != 5)
    {
        throw new Exception("What happened to CallId");
    }
}

This functions as expected. The middleware sets CallId to 5, the WebForms page sees a 5. The exception is not thrown.


Here's where things break intuition, if I want to use an AsyncLocal<int> for my CallId, the value is no longer available in the WebForms page. E.g.

Broken Container

public static class Container
{
    public static AsyncLocal<int> CallId { get; set; }
}

Broken OWIN configuration

[assembly: OwinStartup(typeof(Startup))]

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        app.Use((context, next) =>
        {
            AsyncLocal<int> asyncId = new AsyncLocal<int>();
            asyncId.Value = 5
            Container.CallId = asyncId;

            return next.Invoke();
        });
    }
}

Broken Default.aspx.cs

protected void Page_Load(object sender, EventArgs e)
{
    if (Container.CallId.Value != 5)
    {
        throw new Exception("What happened to CallId");
    }
}

This does not function as expected. An identifier is created in the middleware, but is not available in the page. The exception is thrown.

Why?

I'm struggling to piece together why this update breaks intuition; I can't think of anything that could be causing the value to disappear.

  • The process, domain, and thread id all remain unchanged between the middleware component executing and the webform page's On_Load.
  • Container is static, so only one instance exists in the domain.
  • The difference in behavior doesn't hinge on primitive vs reference types; I tried this same thing with an simple MyType, CallId doesn't return to null in the WebForm

Where did the static CallId value go?

If you want to see this for yourself, here is a demo github repo.

  • Here's the commit where things function intuitively.
  • Here's the commit where things function counter-intuitively.
like image 394
Matt Avatar asked Aug 10 '18 03:08

Matt


1 Answers

You are using it wrong. Use the local to hold the desired value.

public static class Container {
    private static AsyncLocal<int> current = new AsyncLocal<int>();

    public static int CallId { 
        get {
            return current.Value;
        } 
        set {
            current.Value = value;
        }
    }
}

And OWIN configuration remains as it did before.

[assembly: OwinStartup(typeof(Startup))]
public class Startup {
    public void Configuration(IAppBuilder app) {
        app.Use((context, next) => {
            Container.CallId = 5;
            return next.Invoke();
        });
    }
}

the AsyncLocal<T> class will now persist the value across asynchronous flows across threads.

like image 172
Nkosi Avatar answered Oct 05 '22 01:10

Nkosi