>>返回《C# 併發編程》html
[ThreadStatic]
特性、ThreadLocal<T>
、CallContext
、AsyncLocal<T>
都具有這個特性。編程
例子:多線程
因爲 .NET Core 再也不實現 CallContext,因此下列代碼只能在 .NET Framework 中執行併發
class Program { //對照 private static string _normalStatic; [ThreadStatic] private static string _threadStatic; private static ThreadLocal<string> _threadLocal = new ThreadLocal<string>(); private static AsyncLocal<string> _asyncLocal = new AsyncLocal<string>(); static void Main(string[] args) { Parallel.For(0, 4, _ => { var threadId = Thread.CurrentThread.ManagedThreadId; var value = $"這是來自線程{threadId}的數據"; _normalStatic = value; _threadStatic = value; CallContext.SetData("value", value); _threadLocal.Value = value; _asyncLocal.Value = value; Console.WriteLine($"Use Normal; Thread:{threadId}; Value:{_normalStatic}"); Console.WriteLine($"Use ThreadStaticAttribute; Thread:{threadId}; Value:{_threadStatic}"); Console.WriteLine($"Use CallContext; Thread:{threadId}; Value:{CallContext.GetData("value")}"); Console.WriteLine($"Use ThreadLocal; Thread:{threadId}; Value:{_threadLocal.Value}"); Console.WriteLine($"Use AsyncLocal; Thread:{threadId}; Value:{_asyncLocal.Value}"); }); Console.Read(); } }
輸出:異步
Use Normal; Thread:15; Value:10 Use [ThreadStatic]; Thread:15; Value:15 Use Normal; Thread:10; Value:10 Use Normal; Thread:8; Value:10 Use [ThreadStatic]; Thread:8; Value:8 Use CallContext; Thread:8; Value:8 Use [ThreadStatic]; Thread:10; Value:10 Use CallContext; Thread:10; Value:10 Use CallContext; Thread:15; Value:15 Use ThreadLocal; Thread:15; Value:15 Use ThreadLocal; Thread:8; Value:8 Use AsyncLocal; Thread:8; Value:8 Use ThreadLocal; Thread:10; Value:10 Use AsyncLocal; Thread:10; Value:10 Use AsyncLocal; Thread:15; Value:15
結論:async
平常開發過程當中,咱們常常遇到異步的場景。性能
異步可能會致使代碼執行線程的切換。測試
例如:線程
測試:[ThreadStatic]
特性、ThreadLocal<T>
、AsyncLocal<T>
,三種共享變量被異步代碼賦值後的表現。code
class Program { [ThreadStatic] private static string _threadStatic; private static ThreadLocal<string> _threadLocal = new ThreadLocal<string>(); private static AsyncLocal<string> _asyncLocal = new AsyncLocal<string>(); static void Main(string[] args) { _threadStatic = "set"; _threadLocal.Value = "set"; _asyncLocal.Value = "set"; PrintValuesInAnotherThread(); Console.ReadKey(); } private static void PrintValuesInAnotherThread() { Task.Run(() => { Console.WriteLine($"ThreadStatic: {_threadStatic}"); Console.WriteLine($"ThreadLocal: {_threadLocal.Value}"); Console.WriteLine($"AsyncLocal: {_asyncLocal.Value}"); }); } }
輸出:
ThreadStatic: ThreadLocal: AsyncLocal: set
結論:
在異步發生後,線程被切換,只有 AsyncLocal
還可以保留原來的值.
咱們總結一下這些變量的表現:
實現方式 | DotNetFx | DotNetCore | 是否支持數據向輔助線程的 |
---|---|---|---|
[ThreadStatic] | 是 | 是 | 否 |
ThreadLocal
|
是 | 是 | 否 |
CallContext.SetData(string name, object data) | 是 | 否 | 僅當參數 data 對應的類型實現了 ILogicalThreadAffinative 接口時支持 |
CallContext.LogicalSetData(string name, object data) | 是 | 否 | 是 |
AsyncLocal
|
是 | 是 | 是 |
輔助線程: 用於處理後臺任務,用戶沒必要等待就能夠繼續使用應用程序,好比線程池線程。
注意:
[ThreadStatic]
特性、ThreadLocal<T>
最好不要用在線程池線程
AsyncLocal<T>
能夠用在線程池線程
AsyncLocal<T>
的狀態會被清除,沒法訪問以前的值new Task(...)
默認不是新建一個線程,而是使用線程池線程AsyncLocal<T>
的 Value 屬性的真正的數據存取是經過 ExecutionContext 的 internal
的方法 GetLocalValue
和 SetLocalValue
將數據存到 當前ExecutionContext 上的 m_localValues
字段上
new Thread(...).Start()
new Task(...).Start()
Task.Run(...)
ThreadPool.QueueUserWorkItem(...)
await
語法糖m_localValues
類型是 IAsyncLocalValueMap
如下爲基礎設施提供的實現:
類型 | 元素個數 |
---|---|
EmptyAsyncLocalValueMap | 0 |
OneElementAsyncLocalValueMap | 1 |
TwoElementAsyncLocalValueMap | 2 |
ThreeElementAsyncLocalValueMap | 3 |
MultiElementAsyncLocalValueMap | 4 ~ 16 |
ManyElementAsyncLocalValueMap | > 16 |
隨着 ExecutionContext 所關聯的 AsyncLocal 數量的增長, IAsyncLocalValueMap 的實現將會在 ExecutionContext
的 SetLocalValue
方法中被不斷替換。
AsyncLocal
類型存儲數據,是在本身線程的 ExecutionContext 中IAsyncLocalValueMap
類型的變量中,此變量會根據存儲的 AsyncLocal
變量個數而切換實現
參考資料:
《淺析 .NET 中 AsyncLocal 的實現原理》 --- 黑洞視界