.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 }

結果

說明

如果使用了線程池,最好不要使用這種存儲機制了,因為線程池可能不會釋放使用過的線程,導致多次執行之間可能共享數據(可以每次執行前重置線程本地存儲的數據)。

調用上下文

代碼

 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 }

結果

說明

由上圖可以知道,每次執行的數據是完全隔離的,非常符合我們的期望。但是,如果我們期望調用期間又開啟了一個子線程,如何讓子線程訪問父線程的數據呢?這就需要使用到:“邏輯調用上下文”。

邏輯調用上下文

代碼

 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 容器自定義生命周期管理模型。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM