.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 容器自定义生命周期管理模型。
分类: .NET