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