.NET:線程本地存儲、調用上下文、邏輯調用上下文

背景

在多線程環境,若是須要將實例的生命週期控制在某個操做的執行期間,該如何設計?經典的思路是這樣的:做爲參數向調用棧傳遞,如:CommandExecuteContext、HttpContext等。好在不少平臺都提供線程本地存儲這種東西,下面介紹一下 .NET 提供的三種機制。多線程

線程本地存儲

代碼測試

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 using System.Runtime.Remoting;
 8 
 9 namespace ExecutionContextStudy
10 {
11     class ThreadDataSlotTest
12     {
13       public   static void Test()
14         {
15             for (var i = 0; i < 10; i++)
16             {
17                 Thread.Sleep(10);
18 
19                 Task.Run(() =>
20                 {
21                     var slot = Thread.GetNamedDataSlot("test");
22                     if (slot == null)
23                     {
24                         Thread.AllocateNamedDataSlot("test");
25                     }
26 
27                     if (Thread.GetData(slot) == null)
28                     {
29                         Thread.SetData(slot, DateTime.Now.Millisecond);
30                     }
31 
32                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + Thread.GetData(slot));
33                 });
34             }
35 
36             Console.ReadLine();
37         }
38     }
39 }

結果spa

說明線程

若是使用了線程池,最好不要使用這種存儲機制了,由於線程池可能不會釋放使用過的線程,致使屢次執行之間可能共享數據(能夠每次執行前重置線程本地存儲的數據)。設計

調用上下文

代碼code

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 using System.Runtime.Remoting.Messaging;
 8 
 9 namespace ExecutionContextStudy
10 {
11     class CallContextTest
12     {
13         public static void Test()
14         {
15             Console.WriteLine("測試:CallContext.SetData");
16             for (var i = 0; i < 10; i++)
17             {
18                 Thread.Sleep(10);
19 
20                 Task.Run(() =>
21                 {
22                     if (CallContext.GetData("test") == null)
23                     {
24                         CallContext.SetData("test", DateTime.Now.Millisecond);
25                     }
26 
27                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.GetData("test"));
28                 });
29             }
30 
31             Console.ReadLine();
32         }
33     }
34 }

結果blog

說明生命週期

由上圖能夠知道,每次執行的數據是徹底隔離的,很是符合咱們的指望。可是,若是咱們指望調用期間又開啓了一個子線程,如何讓子線程訪問父線程的數據呢?這就須要使用到:「邏輯調用上下文」。it

邏輯調用上下文

代碼io

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 using System.Runtime.Remoting.Messaging;
 8 
 9 namespace ExecutionContextStudy
10 {
11     class ExecutionContextTest
12     {
13         public static void Test()
14         {
15             Console.WriteLine("測試:CallContext.SetData");
16             Task.Run(() =>
17             {
18                 CallContext.SetData("test", "段光偉");
19                 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.GetData("test"));
20 
21                 Task.Run(() =>
22                 {
23                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.GetData("test"));
24                 });
25             });
26 
27             Thread.Sleep(100);
28 
29             Console.WriteLine("測試:CallContext.LogicalSetData");
30             Task.Run(() =>
31             {
32                 CallContext.LogicalSetData("test", "段光偉");
33                 Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.LogicalGetData("test"));
34 
35                 Task.Run(() =>
36                 {
37                     Console.WriteLine(Thread.CurrentThread.ManagedThreadId + ":" + CallContext.LogicalGetData("test"));
38                 });
39 
40                 ExecutionContext.SuppressFlow();
41                 Task.Run(() =>
42                 {
43                     Console.WriteLine("SuppressFlow 以後:" + CallContext.LogicalGetData("test"));
44                 });
45 
46                 ExecutionContext.RestoreFlow();
47                 Task.Run(() =>
48                 {
49                     Console.WriteLine("RestoreFlow 以後:" + CallContext.LogicalGetData("test"));
50                 });
51             });
52 
53             Console.ReadLine();
54         }
55     }
56 }

輸出

說明

注意 ExecutionContext.SuppressFlow(); 和 xecutionContext.RestoreFlow();,它們分別能阻止傳播和重置傳播,默認是容許傳播的。

備註

最多見的使用場景就是:爲 Ioc 容器自定義生命週期管理模型。

相關文章
相關標籤/搜索