簡體   English   中英

使用async / await設置Thread.CurrentPrincipal

[英]Setting Thread.CurrentPrincipal with async/await

下面是我試圖在異步方法中將Thread.CurrentPrincipal設置為自定義UserPrincipal對象的簡化版本,但是自定義對象在離開await后會丟失,即使它仍然在新的threadID 10上。

有沒有辦法在await中更改Thread.CurrentPrincipal並在以后使用它而不傳入或返回它? 或者這不安全,永遠不應該異步? 我知道有線程更改,但認為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
}

我知道有線程更改,但認為async / await將為我處理同步。

async / await本身不會對線程本地數據進行任何同步。 但是,如果你想進行自己的同步,它確實有各種各樣的“鈎子”。

默認情況下,當您await任務時,它將捕獲當前的“上下文”(即SynchronizationContext.Current ,除非它為null ,在這種情況下它是TaskScheduler.Current )。 async方法恢復時,它將在該上下文中恢復。

因此,如果要定義“上下文”,可以通過定義自己的SynchronizationContext 但這並不容易。 特別是如果你的應用程序需要在ASP.NET上運行,那需要自己的AspNetSynchronizationContext (它們不能嵌套或任何東西 - 你只能得到一個)。 ASP.NET使用其SynchronizationContext來設置Thread.CurrentPrincipal

但請注意,與SynchronizationContext有一定的距離 ASP.NET vNext沒有。 OWIN從未做過(AFAIK)。 自托管SignalR也沒有。 通常認為以某種方式傳遞值更合適 - 無論這是對方法是顯式的,還是注入到包含此方法的類型的成員變量中。

如果你真的不想傳遞這個值,那么你可以采用另一種方法:一個async等價的ThreadLocal 核心思想是將不可變值存儲在LogicalCallContext ,該方法由異步方法適當地繼承。 我在我的博客上報道了這個“AsyncLocal” (有傳言說AsyncLocal可能會在.NET 4.6中出現,但在那之前你必須自己動手)。 請注意,您無法使用AsyncLocal技術讀取Thread.CurrentPrincipal ; 您必須更改所有代碼才能使用MyAsyncValues.CurrentPrincipal

Thread.CurrentPrincipal存儲在ExecutionContext中,ExecutionContext存儲在Thread Local Storage中。

在另一個線程上執行委托時(使用Task.Run或ThreadPool.QueueWorkItem),從當前線程捕獲ExecutionContext,並將委托包裝在ExecutionContext.Run中 因此,如果在調用Task.Run之前設置CurrentPrincipal,它仍將在Delegate中設置。

現在您的問題是您更改了Task.Run中的CurrentPrincipal並且ExecutionContext僅以一種方式流動。 我認為這是大多數情況下的預期行為,解決方案是在開始時設置CurrentPrincipal。

在Task中更改ExecutionContext時,您最初想要的是不可能的,因為Task.ContinueWith也捕獲了ExecutionContext。 要做到這一點,你必須在委托運行之后立即以某種方式捕獲ExecutionContext,然后在自定義awaiter的延續中將其回流,但那將是非常邪惡的。

您可以使用自定義awaiter來傳遞CurrentPrincipal (或任何線程屬性)。 下面的例子展示了如何做到這一點,受到Stephen Toub的CultureAwaiter啟發。 TaskAwaiter內部使用TaskAwaiter ,因此也將捕獲同步上下文(如果有)。

用法:

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

代碼(僅經過非常輕微的測試):

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包含SecurityContext ,它包含CurrentPrincipal ,幾乎總是流經所有異步分支。 所以在你的Task.Run()委托中,你 - 在你注意到的一個單獨的線程上,得到相同的CurrentPrincipal 但是,在引擎蓋下,您可以通過ExecutionContext.Run(...)獲取上下文,其中指出:

方法完成時,執行上下文將返回其先前的狀態。

我發現自己處於與Stephen Cleary :)不同的奇怪領域,但我不知道SynchronizationContext與此有什么關系。

Stephen Toub在這里的一篇優秀文章中涵蓋了大部分內容。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM