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
.