清除ExecutionContext,阻止 AsyncLocal 在異步流、Thread中傳遞

前言:

  自從使用了 AsyncLocal 後,就發現 AsyncLocal 變量像個臭蟲一樣,在有 AsyncLocal 變量的線程中啓動的 Task 、或者 Thread 都會附帶 AsyncLocal 變量

  在項目使用 AsyncLocal 實現了全局、局部 工作單元 ,但是就無法在後續作業中開啓多個線程了(需求就是要開啓多個線程,俺也沒得辦法),後續啓動的多線程都會帶有 AsyncLocal 變量,直接導致報錯,例如 DBContext 不是線程安全的錯之類的....。

  其實我一直認爲在一個Http請求中開啓多個線程,不合適,應該把需要執行的任務交給 “後臺工作線程” ,或者交給 “後臺Job” ,但現實世界中的情況就是很複雜,怎麼辦?就是要在Http請求中開啓多個線程,還能怎麼辦呢,去解決 ExecutionContext 、AsyncLocal 傳遞的問題吧。

  “人天天都學到一點東西,而往往所學到的是發現昨日學到的是錯的。

 Thread 中的 ExecutionContext 

  創建一個線程,並啓動,Thread執行的委託中會取到 “AsyncLocalTest.Lang.Value” 在線程外部設置的值。

    

   爲啥Thread會取到外部的 AsyncLocal 變量中的值呢?深入源代碼看下,如下圖。

    

  好傢伙,Thread.Start() 原來線程啓動時,就去執行ExecutionContext.Capture()獲取了線程執行上下文,即 ExecutionContext

  如下圖,可以看到在Thread線程中可以獲取到 ExecutionContext ,從ExecutionContext中可以看到存儲在上面的 AsyncLocal 變量

    

Task中的ExecutionContext 

   聲明Task時,深入源代碼查看

    

   Task 會再執行一個內部構造函數

    

   Task 構造函數中,原來還是通過執行 ExecutionContext.Capture() 獲取了 ExecutionContext

    

   創建一個Task時,Task就自動獲取了“線程執行上下文 即 ExecutionContext”。

阻止ExecutionContext流動

    如何阻止ExecutionContext流動,請查看這篇文章 https://www.cnblogs.com/eventhorizon/p/12240767.html#3executioncontext-%E7%9A%84%E6%B5%81%E5%8A%A8 ,就不再贅述。

實現一個局部乾淨的ExecutionContext

    1.實現一個 DisposeAction ,不知道怎麼稱呼,請看代碼吧,源代碼來只ABP框架,我直接copy過來的。原理,就是Using代碼塊釋放時,執行這個 “Action 委託”。

    /// <summary>
    /// 源代碼來自ABP Vnext框架
    /// </summary>
    public class DisposeAction : IDisposable
    {
        private readonly Action _action;

        public DisposeAction([NotNull] Action action)
        {
            _action = action ?? throw new ArgumentNullException(nameof(action));
        }

        public void Dispose()
        {
            _action();
        }
    }
DisposeAction

    2. 衆所周知 ExecutionContext.SuppressFlow() , 阻斷 ExecutionContext 流動 。ExecutionContext.RestoreFlow(), 啓動 ExecutionContext 流動 。 

    3. 實現局部阻斷 ExecutionContext  流動核心代碼

    public class SuppressExecutionContextFlow
    {
        public static IDisposable CleanEnvironment()
        {
            // 阻斷 ExecutionContext 流動
            ExecutionContext.SuppressFlow();
            return new DisposeAction(() =>
            {
                if (ExecutionContext.IsFlowSuppressed())
                {
                    ExecutionContext.RestoreFlow();
                }
            });
        }
    }
SuppressExecutionContextFlow.CleanEnvironment

    4.測試代碼,隨便調試下

//6.創建一個乾淨的 ExecutionContext 環境,供使用
var scheduler = new QueuedTaskScheduler(2);
AsyncLocalTest.Lang.Value = "test";
using (SuppressExecutionContextFlow.CleanEnvironment())
{
    Task task11 = new Task(() =>
    {
        var aa = ExecutionContext.Capture();
        Console.WriteLine("task11線程:" + AsyncLocalTest.Lang.Value);
    });
    Thread th = new Thread(() =>
    {
        var aa = ExecutionContext.Capture();
        Console.WriteLine("th線程:" + AsyncLocalTest.Lang.Value);
    });
    th.Start();
    task11.Start(scheduler);
}
Console.WriteLine("主線程:" + AsyncLocalTest.Lang.Value);
Console.Read();
乾淨的 ExecutionContext 環境

    調試.gif

    

自此 實現一個局部乾淨的ExecutionContext 完成,我的代碼參考 https://github.com/qiqiqiyaya/Learning-Case/tree/main/CleanExecutionContext

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章