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

 

posted on 2013-11-29 08:58  幸福框架  阅读(4223)  评论(3编辑  收藏  举报

导航

我要啦免费统计