首页 > 代码库 > 寄宿 和 应用程序域(二)

寄宿 和 应用程序域(二)

本篇主要讲述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             //卸载AppDomain26             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中执行,代理返回到domain123             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             //卸载AppDomain42             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 : MarshalByRefObject16     {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             //返回一个Object21             //其值是返回的代理。此创建过程在domain2中执行,代理返回到domain122             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);//通过调用结果可以知道,此执行过程发生在domain137 38             n.class3.DoSomething3(2);39 40             n.CallClass3DoSomething(5);41 42             //上述代码执行完毕,仍回到当前AppDomain,即domain1。43             Console.WriteLine("当前AppDomain名称:" + Thread.GetDomain().FriendlyName);44 45             //卸载AppDomain46             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执行。寄宿可以是互相的。

 

寄宿 和 应用程序域(二)