>>回傳《C# 并發編程》
- 1. 簡介
- 2. 異步下的共享變數
- 3. 決議 AsyncLocal
- 3.1. IAsyncLocalValueMap 的實作
- 3.2. 結論
1. 簡介
- 普通共享變數:
- 在某個類上用靜態屬性的方式即可,
- 多執行緒共享變數
- 希望能將這個變數的共享范圍縮小到單個執行緒內
- 無關系的B執行緒無法訪問到A執行緒的值;
[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 = https://www.cnblogs.com/BigBrotherStone/p/$"這是來自執行緒{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
結論:
- Normal 為對照組
- Nomal 的 Thread 與 Value 值不同,因為讀到了其他執行緒修改的值
- 其他的型別,存盤的值,在 Parallel 啟動的執行緒間是隔離的
2. 異步下的共享變數
日常開發程序中,我們經常遇到異步的場景,
異步可能會導致代碼執行執行緒的切換,
例如:
測驗:[ThreadStatic]特性、ThreadLocal<T> 、AsyncLocal<T> ,三種共享變數被異步代碼賦值后的表現,
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 = https://www.cnblogs.com/BigBrotherStone/p/"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 還能夠保留原來的值.
- CallContext 也可以實作這個需求,但 .Net Core 沒有被實作,這里就不過多說明,
我們總結一下這些變數的表現:
| 實作方式 | 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(...)默認不是新建一個執行緒,而是使用執行緒池執行緒
3. 決議 AsyncLocal
AsyncLocal<T>的 Value 屬性的真正的資料存取是通過 ExecutionContext 的internal的方法GetLocalValue和SetLocalValue將資料存到 當前ExecutionContext 上的m_localValues欄位上- ExecutionContext 會根據執行環境進行流動,詳見 《ExecutionContext(執行背景關系)綜述》
- 簡單描述就是,執行緒發生切換的時候, ExecutionContext 會在前一個執行緒中被捕獲,流向下一個執行緒,它所保存的資料也就隨之流動了
- 在所有會發生執行緒切換的地方,基礎類別庫(BCL) 都為我們封裝好了對 ExecutionContext 的捕獲
- 例如:
new Thread(...).Start()new Task(...).Start()Task.Run(...)ThreadPool.QueueUserWorkItem(...)await語法糖
m_localValues型別是IAsyncLocalValueMap
3.1. IAsyncLocalValueMap 的實作
以下為基礎設施提供的實作:
| 型別 | 元素個數 |
|---|---|
| EmptyAsyncLocalValueMap | 0 |
| OneElementAsyncLocalValueMap | 1 |
| TwoElementAsyncLocalValueMap | 2 |
| ThreeElementAsyncLocalValueMap | 3 |
| MultiElementAsyncLocalValueMap | 4 ~ 16 |
| ManyElementAsyncLocalValueMap | > 16 |
隨著 ExecutionContext 所關聯的 AsyncLocal 數量的增加, IAsyncLocalValueMap 的實作將會在 ExecutionContext 的 SetLocalValue 方法中被不斷替換,
- 查詢的時間復雜度和空間復雜度依次遞增
3.2. 結論
AsyncLocal型別存盤資料,是在自己執行緒的 ExecutionContext 中- ExecutionContext 的實體會隨著異步或者多執行緒的啟動而被流向執行后續代碼的其他執行緒,保證了啟動異步的執行緒存盤的資料可以被訪問到
- 資料存到
IAsyncLocalValueMap型別的變數中,此變數會根據存盤的AsyncLocal變數個數而切換實作- 支持存盤量越大的實作型別,性能越差
參考資料:
《淺析 .NET 中 AsyncLocal 的實作原理》 --- 黑洞視界
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/73201.html
標籤:C#
上一篇:ExecutionContext(執行背景關系)綜述
下一篇:正則運算式提取/過濾字串中的漢字
