我们知道线程是比进程更小的任务调度单位,在.NET中应用程序域(AppDomain)是比进程更小的程序隔离单位,线程可以穿越多个应用程序域执行,同一时刻一个线程只执行在一个应用程序域中。我们还知道应用程序域提供的数据隔离使得在应用程序域之间传递的数据必须是可序列化的或者是 MashalByRefObject 类型。此外,我们还知道线程也通过线程静态字段(ThreadStaticAttribute)或数据槽(LocalDataStoreSlot)提供了数据的线程隔离性,通过线程静态字段或者数据槽,我们总能获得与线程唯一关联的数据存储。但是,当线程在跨越应用程序域执行时,在一个应用程序域中设置的线程静态字段或数据槽的数据在线程执行跨越到其它应用程序域时,这些数据会如何传递呢?今天偶然间想到这个问题,查阅了MSDN后没有找到答案,决定自己实验验证一下。

  在验证之前我猜想有几种可能性:a、维持线程数据的相关性而破坏应用程序域的隔离性,即只要在同一线程中总能直接访问到在其它地方设置的线程数据。b、维持应用程序域的隔离性而不保持线程数据的相关性,即无法访问在其它应用程序域设置的线程数据。c、介于 a 和 b 之间,即在跨越应用程序域时线程数据被序列化或者创建透明代理传递,在其它程序域中执行时访问的线程数据获得的是经过序列化和反序列化得到的新的实例或者透明代理。究竟是哪一种?还是用代码来验证吧。

  创建一个控制台程序,先验证在同一线程下的线程静态字段的线程隔离特性,测试代码如下:

验证线程静态字段的线程隔离性

1 class Program
 2 {
 3     //private static LocalDataStoreSlot _dataSlot;
 4     [ThreadStatic]
 5     private static Data _data;
 6 
 7     static void Main(string[] args)
 8     {
 9         Console.WriteLine("当前线程ID={0}", Thread.CurrentThread.ManagedThreadId);
10         _data = new Data() { ID = 1, Name = "Data1" };
11         Console.WriteLine("Data.ID={0}, Data.Name={1}", _data.ID, _data.Name);
12 
13         Thread thrd = new Thread(delegate()
14         {
15             Console.WriteLine("=========== 非主线程开始 ===========");
16             Console.WriteLine("当前线程ID={0}", Thread.CurrentThread.ManagedThreadId);
17             if (_data == null)
18             {
19                 Console.WriteLine("Data is null ! Thread ID={0}", Thread.CurrentThread.ManagedThreadId);
20             }
21             else
22             {
23                 Console.WriteLine("Data is not null!  Thread ID={0}", Thread.CurrentThread.ManagedThreadId);
24                 Console.WriteLine("Data.ID={0}, Data.Name={1}", _data.ID , _data.Name);
25             }
26             Console.WriteLine("修改数据……Thread ID={0}", Thread.CurrentThread.ManagedThreadId);
27             _data = new Data() { ID = 2, Name = "Data2" };
28             Console.WriteLine("Data.ID={0}, Data.Name={1}    Thread ID={2}", _data.ID, _data.Name, Thread.CurrentThread.ManagedThreadId);
29             Console.WriteLine("=========== 非主线程结束 ===========");
30         });
31 
32         thrd.Start();
33         thrd.Join();
34 
35         Console.WriteLine("当前线程ID={0}", Thread.CurrentThread.ManagedThreadId);
36         Console.WriteLine("Data.ID={0}, Data.Name={1}", _data.ID, _data.Name);
37 
38         Console.ReadLine();
39     }
40 }

    代码输出如下:

当前线程ID=10
    Data.ID=1, Data.Name=Data1
    =========== 非主线程开始 ===========
    当前线程ID=11
    Data is null ! Thread ID=11
    修改数据……Thread ID=11
    Data.ID=2, Data.Name=Data2    Thread ID=11
    =========== 非主线程结束 ===========
    当前线程ID=10
    Data.ID=1, Data.Name=Data1


    

  从输出可以看到在线程(11)中和线程(10)中的 _data 字段是完全隔离的,在线程(10)中_data 保持不变。

  我们接下来添加代码探究线程跨应用程序域时的线程静态字段的传递。在上面的测试代码后面加上以下的代码:

  

代码

// 跨应用程序域执行线程……
Console.WriteLine("\r\n>>>>>>>>>>>>\r\n当前应用程序域:ID={0}, Name={1}", AppDomain.CurrentDomain.Id, AppDomain.CurrentDomain.FriendlyName);
Console.WriteLine("创建 TestDomain 应用程序域……");
AppDomain domain = AppDomain.CreateDomain("TestDomain");
domain.DoCallBack(delegate()
{
    Console.WriteLine("=========== TestDomain 中线程开始执行 ===========");
    Console.WriteLine("当前线程ID={0},当前应用程序域:ID={1},  Name={2}", Thread.CurrentThread.ManagedThreadId, AppDomain.CurrentDomain.Id, AppDomain.CurrentDomain.FriendlyName);
if (_data == null)
    {
        Console.WriteLine("Data is null ! Thread ID={0}", Thread.CurrentThread.ManagedThreadId);
    }
else
    {
        Console.WriteLine("Data is not null!  Thread ID={0}", Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine("Data.ID={0}, Data.Name={1}", _data.ID, _data.Name);
    }
    Console.WriteLine("修改数据……Thread ID={0}", Thread.CurrentThread.ManagedThreadId);
    _data = new Data() { ID = 2, Name = "Data2" };
    Console.WriteLine("Data.ID={0}, Data.Name={1}    Thread ID={2}", _data.ID, _data.Name, Thread.CurrentThread.ManagedThreadId);

    Console.WriteLine("=========== TestDomain 中线程结束执行 ===========");
});

Console.WriteLine("当前线程ID={0},当前应用程序域:ID={1},  Name={2}", Thread.CurrentThread.ManagedThreadId, AppDomain.CurrentDomain.Id, AppDomain.CurrentDomain.FriendlyName);
Console.WriteLine("Data.ID={0}, Data.Name={1}", _data.ID, _data.Name);

Console.ReadLine();

重新运行代码输出如下:

当前线程ID=9
ata.ID=1, Data.Name=Data1
========== 非主线程开始 ===========
当前线程ID=10
ata is null ! Thread ID=10
修改数据……Thread ID=10
ata.ID=2, Data.Name=Data2    Thread ID=10
========== 非主线程结束 ===========
当前线程ID=9
ata.ID=1, Data.Name=Data1>>>>>>>>>>>
当前应用程序域:ID=1, Name=ConsoleApplication1.vshost.exe
创建 TestDomain 应用程序域……
========== TestDomain 中线程开始执行 ===========
当前线程ID=9,当前应用程序域:ID=2,  Name=TestDomain
ata is null ! Thread ID=9
修改数据……Thread ID=9
ata.ID=2, Data.Name=Data2    Thread ID=9
========== TestDomain 中线程结束执行 ===========
当前线程ID=9,当前应用程序域:ID=1,  Name=ConsoleApplication1.vshost.exe
ata.ID=1, Data.Name=Data1

  从输出可以看到,线程(9)是测试程序的主线程,其在进入 TestDomain 之前的输出表明与线程(9)相关的 _data 一直维持不变,而当进入到 TestDomain 中执行时,线程(9)并没有_data,从TestDomain返回到默认程序域执行时,_data 仍然维持其原有的值。这表明线程在跨越应用程序域时,其线程数据是被隔离的,也就是上面的 b 选项成立。

  再做进一步的验证:线程再重新进入 TestDomain 执行,其_data是否保持前一次进入时创建的线程数据。在上面的测试代码之后再加入以下的测试代码:

代码

//再次在 TestDomain 应用程序域执行……"
Console.WriteLine("\r\n>>>>>>>>>>>>>>>>>> 再次在 TestDomain 应用程序域执行……");
domain.DoCallBack(delegate()
{
    Console.WriteLine("=========== TestDomain 中线程开始执行 ===========");
    Console.WriteLine("当前线程ID={0},当前应用程序域:ID={1},  Name={2}", Thread.CurrentThread.ManagedThreadId, AppDomain.CurrentDomain.Id, AppDomain.CurrentDomain.FriendlyName);
if (_data == null)
    {
        Console.WriteLine("Data is null ! Thread ID={0}", Thread.CurrentThread.ManagedThreadId);
    }
else
    {
        Console.WriteLine("Data is not null!  Thread ID={0}", Thread.CurrentThread.ManagedThreadId);
        Console.WriteLine("Data.ID={0}, Data.Name={1}", _data.ID, _data.Name);
    }

    Console.WriteLine("=========== TestDomain 中线程结束执行 ===========");
});

Console.ReadLine();

  重新运行所有代码,输入如下:

>>>>>>>>>>>>>>>>>> 再次在 TestDomain 应用程序域执行……
=========== TestDomain 中线程开始执行 ===========
当前线程ID=9,当前应用程序域:ID=2,  Name=TestDomain
Data is not null!  Thread ID=9
Data.ID=2, Data.Name=Data2
=========== TestDomain 中线程结束执行 ===========

根据以上的输出,可以得出的结论是:线程静态字段(ThreadStaticAttribute)或数据槽(LocalDataStoreSlot)提供的隔离性是针对于特定线程和应用程序域的,而不仅仅是线程。

至此真相大白了。