Why is an "await Task.Yield()" required for Thread.CurrentPrincipal to flow correctly?

Solution 1:

How interesting! It appears that Thread.CurrentPrincipal is based on the logical call context, not the per-thread call context. IMO this is quite unintuitive and I'd be curious to hear why it was implemented this way.


In .NET 4.5., async methods interact with the logical call context so that it will more properly flow with async methods. I have a blog post on the topic; AFAIK that's the only place where it's documented. In .NET 4.5, at the beginning of every async method, it activates a "copy-on-write" behavior for its logical call context. When (if) the logical call context is modified, it will create a local copy of itself first.

You can see the "localness" of the logical call context (i.e., whether it has been copied) by observing System.Threading.Thread.CurrentThread.ExecutionContextBelongsToCurrentScope in a watch window.

If you don't Yield, then when you set Thread.CurrentPrincipal, you're creating a copy of the logical call context, which is treated as "local" to that async method. When the async method returns, that local context is discarded and the original context takes its place (you can see ExecutionContextBelongsToCurrentScope returning to false).

On the other hand, if you do Yield, then the SynchronizationContext behavior takes over. What actually happens is that the HttpContext is captured and used to resume both methods. In this case, you're not seeing Thread.CurrentPrincipal preserved from AuthenticateAsync to GetAsync; what is actually happening is HttpContext is preserved, and then HttpContext.User is overwriting Thread.CurrentPrincipal before the methods resume.

If you move the Yield into GetAsync, you see similar behavior: Thread.CurrentPrincipal is treated as a local modification scoped to AuthenticateAsync; it reverts its value when that method returns. However, HttpContext.User is still set correctly, and that value will be captured by Yield and when the method resumes, it will overwrite Thread.CurrentPrincipal.