Below is a simplified version of where I am trying to set Thread.CurrentPrincipal within an async method to a custom UserPrincipal object but the custom object is getting lost after leaving the await even though it's still on the new threadID 10.
Is there a way to change Thread.CurrentPrincipal within an await and use it later without passing it in or returning it? Or is this not safe and should never be async? I know there are thread changes but thought async/await would handle synching this for me.
[TestMethod]
public async Task AsyncTest()
{
var principalType = Thread.CurrentPrincipal.GetType().Name;
// principalType = WindowsPrincipal
// Thread.CurrentThread.ManagedThreadId = 11
await Task.Run(() =>
{
// Tried putting await Task.Yield() here but didn't help
Thread.CurrentPrincipal = new UserPrincipal(Thread.CurrentPrincipal.Identity);
principalType = Thread.CurrentPrincipal.GetType().Name;
// principalType = UserPrincipal
// Thread.CurrentThread.ManagedThreadId = 10
});
principalType = Thread.CurrentPrincipal.GetType().Name;
// principalType = WindowsPrincipal (WHY??)
// Thread.CurrentThread.ManagedThreadId = 10
}
I know there are thread changes but thought async/await would handle synching this for me.
async
/await
doesn't do any syncing of thread-local data by itself. It does have a "hook" of sorts, though, if you want to do your own syncing.
By default, when you await
a task, it will capture the curent "context" (which is SynchronizationContext.Current
, unless it is null
, in which case it is TaskScheduler.Current
). When the async
method resumes, it will resume in that context.
So, if you want to define a "context", you can do so by defining your own SynchronizationContext
. This is a not exactly easy, though. Especially if your app needs to run on ASP.NET, which requires its own AspNetSynchronizationContext
(and they can't be nested or anything - you only get one). ASP.NET uses its SynchronizationContext
to set Thread.CurrentPrincipal
.
However, note that there's a definite movement away from SynchronizationContext
. ASP.NET vNext does not have one. OWIN never did (AFAIK). Self-hosted SignalR doesn't either. It's generally considered more appropriate to pass the value some way - whether this is explicit to the method, or injected into a member variable of the type containing this method.
If you really don't want to pass the value, then there's another approach you can take as well: an async
-equivalent of ThreadLocal
. The core idea is to store immutable values in a LogicalCallContext
, which is appropriately inherited by asynchronous methods. I cover this "AsyncLocal" on my blog (there are rumors of AsyncLocal
coming possibly in .NET 4.6, but until then you have to roll your own). Note that you can't read Thread.CurrentPrincipal
using the AsyncLocal
technique; you'd have to change all your code to use something like MyAsyncValues.CurrentPrincipal
.