原文地址:http://www.cnblogs.com/xiashengwang/archive/2012/07/17/2594623.html
一,Appdomain概述
CLR COM服务器初始化时,会创建一个AppDomain。AppDomain是一组程序集的逻辑容器。宿主可以通过CLR创建额外的AppDomain。AppDomain的唯一作用就是隔离。下面是它的具体功能。
●一个AppDomain中的代码创建的对象不能由另一个AppDomain中的代码直接访问。
●AppDomain可以卸载。
●AppDomain可以单独保护。AppDomain在创建后,会应用一个权限集,它决定了在这个AppDomain中运行的程序集的最大权限。
●AppDomain可以单独实施配置。AppDomain在创建后,会关联一组配置设置。这些设置主要影响CLR在AppDomain中加载程序集的方式。这些设置涉及搜索路径,版本绑定重定向,卷影复制及加载器优化。
二,AppDomain的进程模型
一个Windows进程中的AppDomain数量没有硬性限制。每个AppDomain都有一个Loader堆,每个Loader堆记录了AppDomain自创建以来访问过的类型,每个类型都有一个方法表,方法表的每个记录项都指向Jit编译的本地代码(前提是该方法至少执行过一次)。我们来看看,在Windows进程中加载AppDomain后的模型图。
如图所示,AppDomain #1中加载了MyApp.exe,TypeLib.dll,System.dll三个程序集;AppDomain #2中加载了WindABC.dll,System.dll两个程序集;System.dll程序集被加载到了两个AppDomain中。如果这两个AppDomain都使用了来自System.dll的一个类型,那么在两个AppDomain的Loader堆中,都会为同一类型分配一个类型对象;类型对象的内存不会为两个AppDomain共享。另外,一个AppDomain中的代码调用一个类型定义的方法时,方法的IL代码会进行JIT编译,生成本地(native)代码将与每个AppDomain相关联;方法的代码不由调用它的所有AppDomain共享。
不共享类型对象的内存和本地代码,这当然是一种浪费。但是,AppDomain的全部目的就是提供隔离性;CLR要求在卸载某个AppDomain并释放它的所有资源的同时,不会对其他的AppDomain产生负面影响。通过复制CLR的数据结构而不共用,就可以保证这一点。除此之外,还能保证多个AppDomain使用的一个类型在AppDomain中都有一组静态字段。
有的程序集本来就要由多个AppDomain使用,最典型的例子就是MSCorLib.dll。该程序集包含了System.Object,System.Int32以及其他所有与.Net Framework密不可分的类型。CLR初始化时,该程序集会自动加载,而且所有的AppDomain都共享该程序集的类型。为了减少资源的消耗,MSCorLib.dll程序集以一种“AppDomain中立”的方式加载。也就是说,针对以“AppDomain中立”方式加载的程序集,CLR会为它们维护一个特殊的Loader堆。该Loader堆中所有的类型对象,以及为这些类型定义的方法JIT编译生成的所有本地代码,都会被进程中的所有AppDomain共享。遗憾的是,共享这些资源带来的收益并不是没有代价的。这个代价就是,以“AppDomain中立”的方式加载的所有程序集永远不能卸载。为了回收他们的资源,唯一的办法就是终止Windows进程,让Windows去回收资源。
三,跨越AppDomain边界访问对象
一个AppDomain中的代码可以和另一个AppDomain中的类型和对象通信。但是,只允许通过良好定义的机制访问这些类型和对象。下面的代码演示了构造以下三种类型时不同的行为:一个“按引用封送”(Marshal-by-Reference),一个“按值封送”(Marshal-by-Value),一个完全不能封送的类型。
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
程序的运行结果如下:
![](https://i-blog.csdnimg.cn/blog_migrate/cdec0645add3fc3c328197dda5c76203.gif)
CLR不允许一个AppDomain中的变量引用另一个AppDomain中创建的对象。如果CreateInstanceAndUnwrap函数只返回对象的引用,AppDomain提供的隔离性就会被打破,而隔离是AppDomain的全部目的!因此在CreateInstanceAndUnwrap返回对象之前,它要执行一些额外的逻辑。
CreateInstanceAndUnwrap导致调用线程从当前AppDomain转至新的AppDomain,它们用的是同一个线程,所有从这一点也可以看出,线程是可以跨越AppDomain的。并且,跨AppDoman边界的方法调用是同步执行的。如果希望多个AppDomain中的代码并非执行,应创建额外的线程。
3.1 “按引用封送”
当CreateInstanceAndUnwrap发现它封送的对象类型派生自MarshalByRefObject,CLR就会跨AppDomain边界按引用封送对象。下面讲述了按引用将一个对象从一个AppDomain(源AppDomain,这里是真正创建对象的地方)封送到另一个AppDomain(目标AppDomain,这里是调用CreateInstanceAndUnwrap的地方)的具体含义。
源AppDomain想向目标AppDomain发送或返回一个对象的引用时,CLR会在目标AppDomain的Loader堆中定义一个代理类型。这个代理类型是用原始类型的元数据生成的。因此,他和原始数据看起来完全一样。有一样的实例成员(事件,属性,方法)。但是实例成员不会成为代理类型的一部分。在这个代理类型中,确实定义了自己的几个实例字段,但这些实例字段和原始数据不一致。相反,这些字段只是用于指出那个AppDomain“拥有”真实的对象,以及如何在拥有对象的AppDomain中找到真实的对象。(在内部,代理对象用一个GCHandle实例引用真实的对象)
这个代理类型在目标AppDomain中定义好之后,CreateInstanceAndUnwrap方法就会创建这个代理类型的实例,初始化它的字段来标识源AppDomain和真实对象,然后将对这个代理对象的引用返回目标AppDomain。CLR一般不允许将一个类型的对象转换成一个不兼容的类型。但在当前这种情况下,CLR允许转型,因为新类型和源类型有相同的实例成员。事实上,用代理对象调用GetType方法,他会向你撒谎,说自己是一个MarshalByRefObject对象。System.Runtime.Remoting.RemotingServices.IsTransparentProxy方法可以用来验证这个对象是一个代理对象。
AppDomain的Unload静态方法会强制CLR卸载指定的AppDomain(包括其中加载的程序集),并强制执行一次垃圾回收,以释放由卸载AppDomain中的代码创建的对象。这时,默认的AppDomain中mbrt变量仍然引用了一个有效的代理对象。但代理对象已不再引用一个有效的AppDomain了(它已经被卸载了)。当试图再次使用代理对象调用SomeMethod方法时,代理的SomeMethod方法会抛出一个AppDomainUnloadedException异常。
由于新创建的AppDomain是没有根的,所以代理引用的原始对象可以被垃圾回收器回收。这当然不理想。但另一方面,如果将原始对象不确定的留在内存中,代理可能不再引用它,而原始对象依然存活,这同样不理想。CLR解决这个问题的办法是使用一个“租约管理器”。一个对象的代理创建好之后,CLR保持对象存活5分钟,如果5分钟之内没有通过代理发出调用,对象就会失效,下次垃圾回收会释放它的对象。每发出一次对对象的调用,“租约管理器”都会续订对象的租期,保证它在接下来的2分钟在内存中保持存活。如果在对象过期之后试图通过一个代理调用它,CLR会抛出一个System.Runtime.Remoting.RemotingException。默认的5分钟和2分钟是可以修改的,你只需要重写MarshalByRefObject的InitializeLifetimeService方法。更多的详情,可以参看SDK文档的“生存期租约”主题。
3.2“按值封送”
按值封送的类型,需要实现Serializable特性。源AppDomain想向目标AppDomain发送或返回一个对象的引用时,CLR将对象的实例字段序列化成一个字节数组。这个字节数组从源AppDomain复制到目标AppDomain。然后在目标AppDomain中反序列化字节数组,这会强制CLR将定义了“被反序列化的类型”的程序集加载到目标AppDomain中(如果还未加载的话)。接着,CLR创建类型的一个实例,并用字节数组中的值初始化对象的字段,使之与原对象的值相同。换言之,CLR在目标AppDomain中复制了源对象。然后CreateInstanceAndUnwrap返回对这个副本的引用;这样一来,对象就跨AppDomain的边界按值封送了。按值封送不会涉及代理,返回的对象被默认的AppDomain“拥有”。
3.3 使用不可封送的类型跨越AppDomain
注意这个例子,调用方法是传给了源AppDomain一个String类型的callingDomainName参数,因为String类有Serializable特性,所以能正常传入。函数返回时,返回了一个没有Serializable特性的类型,所以抛出了异常。