前言: 自從使用了 AsyncLocal 後,就發現 AsyncLocal 變數像個臭蟲一樣,在有 AsyncLocal 變數的線程中啟動的 Task 、或者 Thread 都會附帶 AsyncLocal 變數。 在項目使用 AsyncLocal 實現了全局、局部 工作單元 ,但是就無法在後續作業中 ...
前言:
自從使用了 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
本文來自博客園,作者:youliCC,轉載請註明原文鏈接:https://www.cnblogs.com/youlicc/p/17410530.html