Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting Thread.CurrentPrincipal with async/await

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
}
like image 207
Sean Merron Avatar asked Jul 10 '15 17:07

Sean Merron


People also ask

Does async await use thread pool?

The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active.

Does await pause the thread?

Asynchronous MethodsWhen the executing thread reaches an await expression, it hits the “pause” button and the method execution is suspended. When the task being awaited completes, it hits the “play” button, and the method execution is resumed.

Is async await thread safe?

Using this technique we are always thread safe, and we can actually use a background context to retrieve all Core Data Entities in a background thread. The new get function is async so we need to use await in order to test it. If, in this Unit Test, we add the -com.

Does async await block UI thread?

Though it creates a confusion, in reality async and await will not block the JavaScript main thread. Like mentioned above they are just syntactic sugars for promise chaining.


3 Answers

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.

like image 131
Stephen Cleary Avatar answered Sep 19 '22 18:09

Stephen Cleary


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.

like image 37
Jeff Cyr Avatar answered Sep 18 '22 18:09

Jeff Cyr


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's CultureAwaiter. It uses TaskAwaiter internally, so synchronization context (if any) will be captured, too.

Usage:

Console.WriteLine(Thread.CurrentPrincipal.GetType().Name);

await TaskExt.RunAndFlowPrincipal(() => 
{
    Thread.CurrentPrincipal = new UserPrincipal(Thread.CurrentPrincipal.Identity);
    Console.WriteLine(Thread.CurrentPrincipal.GetType().Name);
    return 42;
});

Console.WriteLine(Thread.CurrentPrincipal.GetType().Name);

Code (only very slightly tested):

public static class TaskExt
{
    // flowing Thread.CurrentPrincipal
    public static FlowingAwaitable<TResult, IPrincipal> RunAndFlowPrincipal<TResult>(
        Func<TResult> func,
        CancellationToken token = default(CancellationToken))
    {
        return RunAndFlow(
            func,
            () => Thread.CurrentPrincipal, 
            s => Thread.CurrentPrincipal = s,
            token);
    }

    // flowing anything
    public static FlowingAwaitable<TResult, TState> RunAndFlow<TResult, TState>(
        Func<TResult> func,
        Func<TState> saveState, 
        Action<TState> restoreState,
        CancellationToken token = default(CancellationToken))
    {
        // wrap func with func2 to capture and propagate exceptions
        Func<Tuple<Func<TResult>, TState>> func2 = () =>
        {
            Func<TResult> getResult;
            try
            {
                var result = func();
                getResult = () => result;
            }
            catch (Exception ex)
            {
                // capture the exception
                var edi = ExceptionDispatchInfo.Capture(ex);
                getResult = () => 
                {
                    // re-throw the captured exception 
                    edi.Throw(); 
                    // should never be reaching this point, 
                    // but without it the compiler whats us to 
                    // return a dummy TResult value here
                    throw new AggregateException(edi.SourceException);
                }; 
            }
            return new Tuple<Func<TResult>, TState>(getResult, saveState());    
        };

        return new FlowingAwaitable<TResult, TState>(
            Task.Run(func2, token), 
            restoreState);
    }

    public class FlowingAwaitable<TResult, TState> :
        ICriticalNotifyCompletion
    {
        readonly TaskAwaiter<Tuple<Func<TResult>, TState>> _awaiter;
        readonly Action<TState> _restoreState;

        public FlowingAwaitable(
            Task<Tuple<Func<TResult>, TState>> task, 
            Action<TState> restoreState)
        {
            _awaiter = task.GetAwaiter();
            _restoreState = restoreState;
        }

        public FlowingAwaitable<TResult, TState> GetAwaiter()
        {
            return this;
        }

        public bool IsCompleted
        {
            get { return _awaiter.IsCompleted; }
        }

        public TResult GetResult()
        {
            var result = _awaiter.GetResult();
            _restoreState(result.Item2);
            return result.Item1();
        }

        public void OnCompleted(Action continuation)
        {
            _awaiter.OnCompleted(continuation);
        }

        public void UnsafeOnCompleted(Action continuation)
        {
            _awaiter.UnsafeOnCompleted(continuation);
        }
    }
}
like image 28
noseratio Avatar answered Sep 20 '22 18:09

noseratio