首页 > 代码库 > 寄宿 和 应用程序域(二)
寄宿 和 应用程序域(二)
本篇主要讲述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执行。寄宿可以是互相的。
寄宿 和 应用程序域(二)