简体   繁体   English

使用async / await设置Thread.CurrentPrincipal

[英]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. 下面是我试图在异步方法中将Thread.CurrentPrincipal设置为自定义UserPrincipal对象的简化版本,但是自定义对象在离开await后会丢失,即使它仍然在新的threadID 10上。

Is there a way to change Thread.CurrentPrincipal within an await and use it later without passing it in or returning it? 有没有办法在await中更改Thread.CurrentPrincipal并在以后使用它而不传入或返回它? 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. 我知道有线程更改,但认为async / await将为我处理同步。

[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将为我处理同步。

async / await doesn't do any syncing of thread-local data by itself. async / await本身不会对线程本地数据进行任何同步。 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 ). 默认情况下,当您await任务时,它将捕获当前的“上下文”(即SynchronizationContext.Current ,除非它为null ,在这种情况下它是TaskScheduler.Current )。 When the async method resumes, it will resume in that context. async方法恢复时,它将在该上下文中恢复。

So, if you want to define a "context", you can do so by defining your own SynchronizationContext . 因此,如果要定义“上下文”,可以通过定义自己的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上运行,那需要自己的AspNetSynchronizationContext (它们不能嵌套或任何东西 - 你只能得到一个)。 ASP.NET uses its SynchronizationContext to set Thread.CurrentPrincipal . ASP.NET使用其SynchronizationContext来设置Thread.CurrentPrincipal

However, note that there's a definite movement away from SynchronizationContext . 但请注意,与SynchronizationContext有一定的距离 ASP.NET vNext does not have one. ASP.NET vNext没有。 OWIN never did (AFAIK). OWIN从未做过(AFAIK)。 Self-hosted SignalR doesn't either. 自托管SignalR也没有。 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 . 如果你真的不想传递这个值,那么你可以采用另一种方法:一个async等价的ThreadLocal The core idea is to store immutable values in a LogicalCallContext , which is appropriately inherited by asynchronous methods. 核心思想是将不可变值存储在LogicalCallContext ,该方法由异步方法适当地继承。 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). 我在我的博客上报道了这个“AsyncLocal” (有传言说AsyncLocal可能会在.NET 4.6中出现,但在那之前你必须自己动手)。 Note that you can't read Thread.CurrentPrincipal using the AsyncLocal technique; 请注意,您无法使用AsyncLocal技术读取Thread.CurrentPrincipal ; you'd have to change all your code to use something like MyAsyncValues.CurrentPrincipal . 您必须更改所有代码才能使用MyAsyncValues.CurrentPrincipal

The Thread.CurrentPrincipal is stored in the ExecutionContext which is stored in the Thread Local Storage. Thread.CurrentPrincipal存储在ExecutionContext中,ExecutionContext存储在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 . 在另一个线程上执行委托时(使用Task.Run或ThreadPool.QueueWorkItem),从当前线程捕获ExecutionContext,并将委托包装在ExecutionContext.Run中 So if you set the CurrentPrincipal before calling Task.Run, it would still be set inside the Delegate. 因此,如果在调用Task.Run之前设置CurrentPrincipal,它仍将在Delegate中设置。

Now your problem is that you change the CurrentPrincipal inside Task.Run and the ExecutionContext is only flowed one way. 现在您的问题是您更改了Task.Run中的CurrentPrincipal并且ExecutionContext仅以一种方式流动。 I think this is the expected behavior in most case, a solution would be to set the CurrentPrincipal at the start. 我认为这是大多数情况下的预期行为,解决方案是在开始时设置CurrentPrincipal。

What you originally want is not possible when changing the ExecutionContext inside a Task, because Task.ContinueWith capture the ExecutionContext too. 在Task中更改ExecutionContext时,您最初想要的是不可能的,因为Task.ContinueWith也捕获了ExecutionContext。 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,然后在自定义awaiter的延续中将其回流,但那将是非常邪恶的。

You could use a custom awaiter to flow CurrentPrincipal (or any thread properties, for that matter). 您可以使用自定义awaiter来传递CurrentPrincipal (或任何线程属性)。 The below example shows how it might be done, inspired by Stephen Toub's CultureAwaiter . 下面的例子展示了如何做到这一点,受到Stephen Toub的CultureAwaiter启发。 It uses TaskAwaiter internally, so synchronization context (if any) will be captured, too. TaskAwaiter内部使用TaskAwaiter ,因此也将捕获同步上下文(如果有)。

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);
        }
    }
}

ExecutionContext , which contains SecurityContext , which contains CurrentPrincipal , is pretty-much always flowed across all asynchronous forks. ExecutionContext包含SecurityContext ,它包含CurrentPrincipal ,几乎总是流经所有异步分支。 So in your Task.Run() delegate, you - on a separate thread as you note, get the same CurrentPrincipal . 所以在你的Task.Run()委托中,你 - 在你注意到的一个单独的线程上,得到相同的CurrentPrincipal However, under the hood, you get the context flowed via ExecutionContext.Run(...) , which states: 但是,在引擎盖下,您可以通过ExecutionContext.Run(...)获取上下文,其中指出:

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 Cleary :)不同的奇怪领域,但我不知道SynchronizationContext与此有什么关系。

Stephen Toub covers most of this in an excellent article here . Stephen Toub在这里的一篇优秀文章中涵盖了大部分内容。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM