背景
在多线程环境,如果需要将实例的生命周期控制在某个操作的执行期间,该如何设计?经典的思路是这样的:作为参数向调用栈传递,如: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 ExecutionContextStudy10 {11 class ThreadDataSlotTest12 {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 ExecutionContextStudy10 {11 class CallContextTest12 {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 ExecutionContextStudy10 {11 class ExecutionContextTest12 {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 容器自定义生命周期管理模型。