寄宿 和 应用程序域(二)

本篇主要讲述AppDomain的使用。

基础部分请看上一篇 寄宿 和 应用程序域(一)

先看一个使用AppDomain的实例1:

寄宿代码如下:

 1 namespace LibraryOne
 2 {
 3     [Serializable]
 4     public class Class1
 5     {
 6         public void DoSomething(int max)
 7         {
 8             Console.WriteLine("当前AppDomain:{0}",System.Threading.Thread.GetDomain().FriendlyName);
 9             for (int i = 0; i < max; i++)
10             {
11                 Console.WriteLine(i);
12             }
13         }
14     }
15 }

调用代码如下: 

 1 namespace MyProject
 2 {
 3     class Program
 4     {
 5         static void Main()
 6         {
 7             //获取当前默认AppDomain
 8             AppDomain domain1 = Thread.GetDomain();
 9 
10             Console.WriteLine("默认AppDomain名称:" + domain1.FriendlyName);
11 
12             //创建新的AppDomain           
13             //此处传递null,使domain2的权限及配置信息与当前AppDomain相同。
14             AppDomain domain2 = AppDomain.CreateDomain("domainname2", null, null);
15 
16             Console.WriteLine("domain2名称:" + domain2.FriendlyName);
17 
18             //加载独立程序集
19             var assembly = Assembly.LoadFrom("LibraryOne.dll");
20            
21             var s = (LibraryOne.Class1)domain2.CreateInstanceAndUnwrap(assembly.FullName, "LibraryOne.Class1");
22 
23             s.DoSomething(3);
24 
25             //卸载AppDomain
26             AppDomain.Unload(domain2);
27 
28             Console.ReadKey();
29         }
30     }
31 }

结果:

可以发现,DoSomething()的执行,发生在默认AppDomain,即domain1。为什么?(后面解答)

 以上是一个AppDomain使用的简单实例,其中包含了AppDomain从创建,调用,到卸载的过程。

AppDomain 创建
  AppDomain提供了静态方法CreateDomain(),创建AppDomain
  AppDomain.CreateDomain提供了5种重载方式,可以用于定义所创建AppDomain的 友好名称,初始化信息,权限信息,受信任程序集等信息。


AppDomain 卸载
  AppDomain 提供了静态方法AppDomain.Unload(),用于卸载指定的 AppDomain 。
  卸载过程大致如下:
  1、CLR挂起进程中所有执行过寄宿代码的线程。
  2、检查上述所有线程的线程栈,对于正在执行 或即将执行 被卸载AppDomain的线程,CLR会强迫其抛出异常ThreadAbortExecption。这将导致异常处理块finally中的代码执行,以清理资源。
    如果没有异常捕捉,CLR使相关线程终止,进程继续运行。
  3、当上一步所有线程都离开 被卸载AppDomain后,CLR遍历堆,为所有与 被卸载AppDomain 有关的代理设置一个标识,是这些代理知道其所引用的真实对象已经不存在。
    此时,任何代码对这些代理的方法调用,都会抛出AppDomainUnloadException.
  4、CLR强制垃圾回收,对已卸载AppDomain创建的任何对象进行内存回收。
  5、CLR恢复剩余所有线程的执行。

对于AppDomian的调用执行,在下面实例中可以看到。

AppDomain 间通信:
  AppDomain提供的主要功能就是隔离。但有时我们有需要在AppDomain间通信。
  CRL提供了一些机制用于AppDomain间的通信,当然这种通信并不破坏 AppDomain的隔离性。

一、按引用封送的 跨域传值
  假设AppDomain1 要与AppDomain2中的寄宿代码通信。
  按引用封送的的做法是,在AppDomain2中创建寄宿代码的类型实例,当然此时不能返回该实例,返回了该实例,AppDomain的隔离就无从谈起了。
  而是返回给AppDomain1一个代理,这个代理中包含了一些信息。通过这些信息,我们可以知道创建实例的AppDomain,以及如何在这个AppDomain中找到这个实例。
  于是在AppDomain1中,我们通过这个代理来对AppDomain2中实例进行调用。执行时,线程会根据代理中的信息返回AppDomain2中执行具体的代码。
  通过这种方式,实现了AppDomain间的通信,同时又保持了隔离的特点。
  若要实现按引用封送的 跨域传值,寄宿代码必须继承类型:MarshalByRefObject

演示实例2

寄宿代码:

 1 namespace LibraryOne
 2 {
 3     //若要实现按引用封送的 跨域传值,寄宿代码必须继承类型:MarshalByRefObject
 4     public class Class2:MarshalByRefObject
 5     {
 6         public void DoSomething2(int max)
 7         {
 8             Console.WriteLine("当前AppDomain:" + System.Threading.Thread.GetDomain().FriendlyName);
 9             for (int i = 0; i < max; i++)
10             {
11                 Console.WriteLine(i);
12             }
13         }
14     }
15 }

调用代码:

 1 namespace MyProject
 2 {
 3     class Program
 4     {
 5         static void Main()
 6         {            
 7             //获取当前默认AppDomain
 8             AppDomain domain1 = Thread.GetDomain();
 9 
10             Console.WriteLine("默认AppDomain名称:" + domain1.FriendlyName);
11 
12             //创建新的AppDomain            
13             AppDomain domain2 = AppDomain.CreateDomain("domainname2", null, null);
14 
15             Console.WriteLine("domain2名称:" + domain2.FriendlyName);
16 
17             //加载独立程序集
18             var assembly = Assembly.LoadFrom("LibraryOne.dll");
19 
20             //返回一个ObjectHandle,ObjectHandle也继承了MarshalByRefObject。
21             //其值是返回的代理。代理中有成员保存了该代理实例创建自domain2,程序集为LibraryOne,类型为Class2,以及在domain2中如何找到Class2实例等信息。
22             //此创建过程在domain2中执行,代理返回到domain1
23             var s = domain2.CreateInstance(assembly.FullName, "LibraryOne.Class2");
24             
25             //返回ObjectHandle包装的对象。类型为object。值仍为代理。
26             var m = s.Unwrap();
27 
28             //类型转换,转换为Class2,值仍为代理。
29             var n = (LibraryOne.Class2)m;
30 
31             //验证是否为代理
32             Console.WriteLine("Is proxy:{0}", RemotingServices.IsTransparentProxy(n));
33 
34             //调用Class2中的方法。
35             //此代码执行时,会根据代理中信息回到domain2中找到Class2实例并执行。
36             n.DoSomething2(2);
37 
38             //上述代码执行完毕,仍回到当前AppDomain,即domain1。
39             Console.WriteLine("当前AppDomain名称:" + Thread.GetDomain().FriendlyName);
40 
41             //卸载AppDomain
42             AppDomain.Unload(domain2);
43 
44             Console.ReadKey();
45         }
46     }
47 }

 结果:

n是一个代理,DoSomething2()的执行发生在domain2中。

 

二、按值封送的 跨域传值

  按值封送的的跨域通信,的处理方式是。结合下面实例3来看。

  domain1为当前AppDomain,domain2为寄宿代码运行的AppDomain。

  这里按值封送的的跨域通信的做法是,先按照按引用封送的的方式获取一个Class4实例的代理A。

  Class4实例提供的方法RetrunClass3()可以返回一个 可序列化的Class3实例。

  此时通过代理A,调用RetrunClass3方法,线程会返回domain2中创建一个Class3对象B。

  此时,domain2向domain1返回B的引用时。CLR会将B序列化,并将序列化后的数据返回给domain1,domain1通过反序列化在domain1中得到一个B对象的复制品C。

  通过这种方式,我们可以把domain2中的实例,拿到domain1中使用。此时,源实例B仍存在于domian2中。

演示实例3:

寄宿代码:

 1 namespace LibraryOne
 2 {
 3     [Serializable]
 4     public class Class3
 5     {
 6         public void DoSomething3(int max)
 7         {
 8             Console.WriteLine("当前AppDomain:" + System.Threading.Thread.GetDomain().FriendlyName);
 9             for (int i = 0; i < max; i++)
10             {
11                 Console.WriteLine(i);
12             }
13         }
14     }
15     public class Class4 : MarshalByRefObject
16     {
17         public Class3 class3 = null;
18         public Class3 RetrunClass3()
19         {
20             Console.WriteLine("当前AppDomain:" + System.Threading.Thread.GetDomain().FriendlyName);
21             if (class3 == null) class3 = new Class3();
22             return class3;
23         }
24         public void DoSomething4(int max)
25         {
26             Console.WriteLine("当前AppDomain:" + System.Threading.Thread.GetDomain().FriendlyName);
27             for (int i = 0; i < max; i++)
28             {
29                 Console.WriteLine(i);
30             }
31         }
32         public void CallClass3DoSomething(int max)
33         {
34             if (class3 != null) class3.DoSomething3(max);
35         }
36     }
37 
38 }

调用代码:

 1 namespace MyProject
 2 {
 3     class Program
 4     {
 5         static void Main()
 6         {
 7             //获取当前默认AppDomain
 8             AppDomain domain1 = Thread.GetDomain();
 9 
10             Console.WriteLine("默认AppDomain名称:" + domain1.FriendlyName);
11 
12             //创建新的AppDomain            
13             AppDomain domain2 = AppDomain.CreateDomain("domainname2", null, null);
14 
15             Console.WriteLine("domain2名称:" + domain2.FriendlyName);
16 
17             //加载独立程序集
18             var assembly = Assembly.LoadFrom("LibraryOne.dll");
19 
20             //返回一个Object
21             //其值是返回的代理。此创建过程在domain2中执行,代理返回到domain1
22             var s = domain2.CreateInstanceAndUnwrap(assembly.FullName, "LibraryOne.Class4");
23 
24             //类型转换,转换为Class4,值仍为代理。
25             var n = (LibraryOne.Class4)s;
26 
27             //验证是否为代理
28             Console.WriteLine("Is proxy:{0}", RemotingServices.IsTransparentProxy(n));
29 
30             //调用Class4中的方法。此代码执行时,会根据代理中信息回到domain2中找到Class4实例并执行。
31             n.DoSomething4(4);
32 
33             var c3 = n.RetrunClass3();
34             //验证是否为代理
35             Console.WriteLine("Is proxy:{0}", RemotingServices.IsTransparentProxy(c3));
36             c3.DoSomething3(3);//通过调用结果可以知道,此执行过程发生在domain1
37 
38             n.class3.DoSomething3(2);
39 
40             n.CallClass3DoSomething(5);
41 
42             //上述代码执行完毕,仍回到当前AppDomain,即domain1。
43             Console.WriteLine("当前AppDomain名称:" + Thread.GetDomain().FriendlyName);
44 
45             //卸载AppDomain
46             AppDomain.Unload(domain2);
47 
48             Console.ReadKey();
49         }
50     }
51 }

调用36行 c3.DoSomething3(3)后

结果:

通过输出可以看到,这个过程是在domain1中执行的。为什么?

c3是domain2中源实例在domain1中的复制品,并且是一个完整的对象,它无法穿过AppDomain去调用domain2中的源实例,只能在domain1中执行。

 

那么有没有方法可以执行到domain2中的源对象呢?Class4中有个源对象的实例。我们调用试试。 

代码38行  n.class3.DoSomething3(2)

结果:

通过调用结果,看到任然是在domain1中执行的。为什么?

我们知道,此时n是代理,通过这个代理获取class3时,由于class3是可序列化的。返回给我们的仍然是class3的复制品,这中间仍然存在序列化反序列化的过程发生。

 

  实际上 这个例子并不是按值封送 最直接的例子。本章的实例1  才是按值封送 最简单直接的例子。

  实例1中,由于Class1是可以序列化的,通过代理对其进行调用时。会返回给宿主AppDomain一个反序列化的Class1实例,所以在实例1中,通过输出结果可以看到,执行时发生在domain1中的。

  在这个例子之所以稍复杂,是为了在讲述按值封送的同时 演示两种封送方式的配合使用。

按值封送的关键是被封送的类型必须可序列化。只要通过代理去获取可序列化的对象,都会在宿主AppDomain中得到一个反序列化后的实例。

 

那么,我有方法可以调用到domain2中的源对象吗?

有的,但是要通过代理中的方法去调用,而不能直接通过代理去获取对象。

代码40行  n.CallClass3DoSomething(5);

结果:

看到这个输出结果,可以发现,执行时在domain2中发生的。

 

实际上按引用封送的,可以方便我们将domain1中的代码传入domain2中执行。

按值封送的 可以让我们将domain2中代码拿到domain1中执行。

将这两种方式结合起来,我们就可以让代码根据我们的需求穿梭于domain1和domain2之间。同时,当前线程也穿梭于两个AppDomain之间工作。

 

虽然演示实例中,都是domain2寄宿于domain1。实际上,我们也可以让domain1的代码寄宿于domain2执行。寄宿可以是互相的。

 

原文地址:https://www.cnblogs.com/qingzhuo/p/3945082.html