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
}
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 isSynchronizationContext.Current
, unless it isnull
, in which case it isTaskScheduler.Current
). When theasync
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 ownAspNetSynchronizationContext
(and they can't be nested or anything - you only get one). ASP.NET uses itsSynchronizationContext
to setThread.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 ofThreadLocal
. The core idea is to store immutable values in aLogicalCallContext
, which is appropriately inherited by asynchronous methods. I cover this "AsyncLocal" on my blog (there are rumors ofAsyncLocal
coming possibly in .NET 4.6, but until then you have to roll your own). Note that you can't readThread.CurrentPrincipal
using theAsyncLocal
technique; you'd have to change all your code to use something likeMyAsyncValues.CurrentPrincipal
.The Thread.CurrentPrincipal is stored in the ExecutionContext which is stored in the Thread Local Storage.
When executing a delegate on another thread (with Task.Run or ThreadPool.QueueWorkItem) the ExecutionContext is captured from the current thread and the delegate is wrapped in ExecutionContext.Run. So if you set the CurrentPrincipal before calling Task.Run, it would still be set inside the Delegate.
Now your problem is that you change the CurrentPrincipal inside Task.Run and the ExecutionContext is only flowed one way. I think this is the expected behavior in most case, a solution would be to set the CurrentPrincipal at the start.
What you originally want is not possible when changing the ExecutionContext inside a Task, because Task.ContinueWith capture the ExecutionContext too. To do it you would have to capture somehow the ExecutionContext right after the Delegate is ran and then flowing it back in a custom awaiter's continuation, but that would be very evil.
ExecutionContext
, which containsSecurityContext
, which containsCurrentPrincipal
, is pretty-much always flowed across all asynchronous forks. So in yourTask.Run()
delegate, you - on a separate thread as you note, get the sameCurrentPrincipal
. However, under the hood, you get the context flowed via ExecutionContext.Run(...), which states:I find myself in strange territory differing with Stephen Cleary :), but I don't see how
SynchronizationContext
has anything to do with this.Stephen Toub covers most of this in an excellent article here.
You could use a custom awaiter to flow
CurrentPrincipal
(or any thread properties, for that matter). The below example shows how it might be done, inspired by Stephen Toub'sCultureAwaiter
. It usesTaskAwaiter
internally, so synchronization context (if any) will be captured, too.Usage:
Code (only very slightly tested):