0%

AsyncLocal and ThreadLocal

ThreadLocal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static ThreadLocal<string> LocalString = new ThreadLocal<string>();

static async Task Main(string[] args)
{
LocalString.Value = "Value 1";
Console.WriteLine($"【A】 Thread: {Thread.CurrentThread.ManagedThreadId}, ExcutionContext: {Thread.CurrentThread.ExecutionContext.GetHashCode()} ,value: {LocalString.Value}");

var t1 = AsyncMethod();

Console.WriteLine($"【D】 Thread: {Thread.CurrentThread.ManagedThreadId}, ExcutionContext: {Thread.CurrentThread.ExecutionContext.GetHashCode()} ,value: {LocalString.Value}");

await t1;
}

static async Task AsyncMethod()
{
Console.WriteLine($"【B】 Thread: {Thread.CurrentThread.ManagedThreadId}, ExcutionContext: {Thread.CurrentThread.ExecutionContext.GetHashCode()} ,value: {LocalString.Value}");

LocalString.Value = "Value 3";
Console.WriteLine($"【C】 Thread: {Thread.CurrentThread.ManagedThreadId}, ExcutionContext: {Thread.CurrentThread.ExecutionContext.GetHashCode()} ,value: {LocalString.Value}");

await Task.Delay(100);

Console.WriteLine($"【E】 Thread: {Thread.CurrentThread.ManagedThreadId}, ExcutionContext: {Thread.CurrentThread.ExecutionContext.GetHashCode()} ,value: {LocalString.Value}");
}


//【A】 Thread: 14, ExcutionContext: 37151951 ,value: Value 1
//【B】 Thread: 14, ExcutionContext: 51676517 ,value: Value 1
//【C】 Thread: 14, ExcutionContext: 51676517 ,value: Value 3
//【D】 Thread: 14, ExcutionContext: 37151951 ,value: Value 3
//【E】 Thread: 16, ExcutionContext: 46128400 ,value:

ThreadLocal 非常容易理解, 每個 Thread 之間彼此是隔離的, 即便 ExcutionContext 不同, 但只要是同一個 Thread 都會是共用的。

所以可以看到 await 之後, 因為不同 Thread 執行剩下的 Code , ThreadLocal 的值就變成預設的空值


AsyncLocal

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
static AsyncLocal<string> LocalString = new AsyncLocal<string>();

static async Task Main(string[] args)
{
LocalString.Value = "Value 1";
Console.WriteLine($"【A】 Thread: {Thread.CurrentThread.ManagedThreadId}, ExcutionContext: {Thread.CurrentThread.ExecutionContext.GetHashCode()} ,value: {LocalString.Value}");

var t1 = AsyncMethod();

Console.WriteLine($"【D】 Thread: {Thread.CurrentThread.ManagedThreadId}, ExcutionContext: {Thread.CurrentThread.ExecutionContext.GetHashCode()} ,value: {LocalString.Value}");

await t1;
}

static async Task AsyncMethod()
{
Console.WriteLine($"【B】 Thread: {Thread.CurrentThread.ManagedThreadId}, ExcutionContext: {Thread.CurrentThread.ExecutionContext.GetHashCode()} ,value: {LocalString.Value}");

LocalString.Value = "Value 3";
Console.WriteLine($"【C】 Thread: {Thread.CurrentThread.ManagedThreadId}, ExcutionContext: {Thread.CurrentThread.ExecutionContext.GetHashCode()} ,value: {LocalString.Value}");

await Task.Delay(100);

Console.WriteLine($"【E】 Thread: {Thread.CurrentThread.ManagedThreadId}, ExcutionContext: {Thread.CurrentThread.ExecutionContext.GetHashCode()} ,value: {LocalString.Value}");
}



//【A】 Thread: 14, ExcutionContext: 5705933 ,value: Value 1
//【B】 Thread: 14, ExcutionContext: 12517624 ,value: Value 1
//【C】 Thread: 14, ExcutionContext: 12517624 ,value: Value 3
//【D】 Thread: 14, ExcutionContext: 5705933 ,value: Value 1
//【E】 Thread: 8, ExcutionContext: 35765882 ,value: Value 3

AsyncLocal 會在每次需要切出 ExcutionContext 時複製一份給新的 ExcutionContext , 所以每個 ExcutionContext間的 AsyncLocal 都是獨立的,但同時可達跨 Thread , ExcutionContext 往下傳遞的特性。