The execution context gets confused with synchronization context, read this post if you're confused https://devblogs.microsoft.com/pfxteam/executioncontext-vs-synchronizationcontext/. The execution context existed before async locals and async methods so lots of .NET APIs are aware of it. Even ones you wouldn't expect... #dotnet
At the lowest level, and API can choose to capture the execution context and then run a callback under that context. This callback will revert to the existing context on the way out. The below will print 10 then 0.
We captured the execution context when the value of the async local was 10. So that value is being stashed and restored we execute the call later under the same context. Why is this important? Well APIs that store user callbacks tend to store the execution context.
What do I mean? Let's replace the explicit capture with an API that captures the context, a Timer. The below code will capture the value of the async local and will use it to execute timer callbacks. It will print 10 forever.
What do you think these print? Yup, 10. You see the pattern. These APIs are trying to preserve the execution context because they assume that's the best behavior.
This means async locals are sticky to these APIs, sorta like fly paper (the flies are the async locals). It'll capture the state even if you didn't mean it to. So how do you disable the capture? There's 2 ways
You can suppress the flow explicitly. This only works if the flow hasn't already been suppressed by another call before you (you can check before suppressing the flow).
OR you can pray the API has an "unsafe" alternative. Unsafe in the threading APIs means "don't capture the execution context", it really has nothing to do with the unsafe keyword in C# (I wasn't there when this pattern was invented 😁):
These APIs exists in a bunch of places but don't exist everywhere https://github.com/dotnet/runtime/issues/24770 so sometimes explicit suppression is required.
When is this behavior bad, well let's look at an example where it caused a memory leak in a customer's http://ASP.NET  Core application once. When using the IHttpContextAccessor, it stashes the HttpContext in an AsyncLocal.
This means that any API that captures the execution context would capture the HttpContext and hold onto it as long as they held onto that context. If we take a memory snapshot (using visual studio) after an "induced GC" (call to GC.Collect) you can see we're leaking HttpContexts:
This bug is worse because of mismatched lifetimes between the CancellationToken and the ExecuteRequest method. Also dispose your disposables!
Mismatched lifetimes are common during lazy initialization of resources. Now there'll be a single StrongBox<HttpContext> leaked until this timer goes away (if it ever does...)
You can follow @davidfowl.
Tip: mention @twtextapp on a Twitter thread with the keyword “unroll” to get a link to it.

Latest Threads Unrolled: