Setting Thread.CurrentPrincipal with async/await
Asked Answered
T

5

19

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
}
Tomasz answered 10/7, 2015 at 17:10 Comment(4)
Are you working with ASP.NET, if so you may want to switch to HttpContext.Current.User, that gives you the behavior you are looking for.Aklog
Hehe, I thought this would come up and should have specified that this has to work for non-web apps where HttpContext is not available.Tomasz
I did not think you where (You very rarely want to do Task.Run in a web app).Aklog
Yeah and if I refactor out the Task.Run logic into SomeAsyncMethod and then just await SomeAsyncMethod, the same result appears where Thread.CurrentPrincipal is still a WindowsPrincipal after the await instead of a UserPrincipal.Tomasz
C
8

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);
        }
    }
}
Circumambulate answered 12/7, 2015 at 0:34 Comment(1)
I think this is the best chance to make this work so I'll see what I can do with it. Thanks for taking the time to put this together!Tomasz
P
15

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.

Preadamite answered 10/7, 2015 at 18:5 Comment(8)
AsyncLocal in 4.6 is not a rumor, it's there with no warning that this feature may be removed before release.Aklog
Thank you for your quick reply Stephen! I was hoping this would get your attention :-) Is this true when dealing with objects in a particular circumstance or just a special case for Thread.CurrentPrincipal? For instance, I can have an Animal that is of the subtype Cat and within an awaited Task change it to a subtype Dog and it is still a Dog after the await.Tomasz
@SeanMerron: It's true for all thread-local variables (where each thread has its own independent value). If your variable is plain static (one value shared between all threads) or local, then there's no problem.Preadamite
@SeanMerron Since you were initially in the ThreadPool before awaiting Task.Run, it will continue synchronously in the same thread, so your ThreadLocal static variable will still be set. But Thread.CurrentThread is stored in the ExecutionContext which is handled differently, see my answer for more details.Recreation
@StephenCleary Great article about AsyncLocal, but I think it would not work for his scenario. The LogicalCallContext will flow one way from the caller to the callee, if the callee add or change a value it won't flow back to the caller right?Recreation
@JeffCyr: That is correct. Async locals only flow from caller to callee, so the op would have to set the value before the call.Preadamite
So if he has to set Thread.CurrentPrincipal before the call, it will be flowed as a part of ExecutionContext anyway into the Task.Run lambda and there'd be no need for AsyncLocal, AFAIU.Circumambulate
@Noseratio: Good point; in this scenario, AsyncLocal wouldn't provide any benefit over what CurrentPrincipal already does.Preadamite
R
10

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.

Recreation answered 10/7, 2015 at 18:49 Comment(1)
Thanks for explaining the flow Jeff. I'm debating on the custom route but as you mentioned, it's scary.Tomasz
C
8

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);
        }
    }
}
Circumambulate answered 12/7, 2015 at 0:34 Comment(1)
I think this is the best chance to make this work so I'll see what I can do with it. Thanks for taking the time to put this together!Tomasz
D
6

ExecutionContext, which contains SecurityContext, which contains CurrentPrincipal, is pretty-much always flowed across all asynchronous forks. So in your Task.Run() delegate, you - on a separate thread as you note, get the same CurrentPrincipal. However, under the hood, you get the context flowed via ExecutionContext.Run(...), which states:

The execution context is returned to its previous state when the method completes.

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.

Driving answered 10/7, 2015 at 21:18 Comment(2)
I agree. A custom SynchronizationContext wouldn't help because CurrentPrincipal would be restored by the time SynchronizationContext.Post is called as a part of await continuation. A custom awaiter might be an option, but there's always a possible race condition that the task completes before the awaiter's ICriticalNotifyCompletion.OnCompleted is called.Circumambulate
Great article! Thanks sellotape!Tomasz
B
1

I work on a widely used asp.net software product that has been around for over 10 years ... so well before async/await was introduced - it is now .net Framework 4.8. Parts of it have used a setup like the questioners where we use the thread to setup a principal:

public static void SetContext(int id, string name)
{
    var context = new LoadContext(id);
    var culture = context.Region.CultureInfo;

    Thread.CurrentPrincipal = new PTPrincipal(name, context);
    Thread.CurrentThread.CurrentCulture = culture;
    Thread.CurrentThread.CurrentUICulture = culture;
}

This has a problem similar to the questioners in that when the async await is used this context setup gets lost. The fix has been that the awaited calls use the ConfigureAwait(false) on every single awaited call right the way through the call stack ie

private async Task DoSomething()
{
    await Task.Delay(1000).ConfigureAwait(false);
}

.....

await DoSomething().ConfigureAwait(false);

So, for us the fix is to always use the .ConfigureAwait(false)

Batik answered 16/10, 2023 at 0:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.