前一个项目,有机会学了AppDomian基本概念。把学习心得写下来。范例可以在这里下载
AppDomain的作用
AppDomain 也叫应用程序域,是CLR创建的,用来规范代码的执行范围,并提供独立的错误隔离机制。因此,每个应用程序包可以包含一个或多个AppDomain,每个AppDomain能够独立卸载及管理Assembly。
因为AppDomain也是独立存在互不影响,而且创建一个AppDomain和跨AppDomain的开销都比进程要低得多,所以常常使用独立AppDomain来提供服务,避免由于程序某部分出错而导致整个程序崩溃。
示例简述
通过调用不同AppDomain中SayHi的对象 实现了 中文,英文,同时使用中英文的SayHi方法,以及异常问题。
- MultiAppDomain 项目:是默认程序域的所在项目。用于创建副AppDomain,并且调用副AppDomain里面的服务实现SayHi的调用
- Interface项目。只有一个接口,用于规范SayHi的调用方式。
- SayHiChinese::实现中文SayHi
- SayHiEnglish: 实现英文SayHI
- SayHiBoth:调用副AppDomain中的实现中英文SayHi
- SayHiException:在条用过程中抛出一个异常,并且检查副AppDomain中的状态。
AppDomain的基本方法
默认AppDomain就是在应用程序一开始运行所在的程序域。而副AppDomain指的就是在默认程序域或者其他程序域下创建的程序域。
- 创建一个AppDomain。
无论在程序域中或者副AppDoman中都可以创建新的AppDomain,而这个程序域就是受到当前程序域管理的。如:
输入参数味AppDomain的名称,这个名称可以重复。也就是你可以有多个叫做chinese的的程序域
默认地 chineseAppDomain. BaseDirectory指向的目录是默认程序域的目录。当程序运行的时候,需要元数据,会后首先从这个目录开始查找。
- 卸载一个程序域。
默认程序域是不能被卸载的,否则回抛出CannotUnloadAppDomainException 错误。
如果是副程序域自己卸载自己,哪么抛出内部抛出System.Threading.ThreadAbortException 异常,但是在外部可以捕获System.AppDomainUnloadedException。
- 获取当前的AppDomain
如果当前在默认程序域中,哪么就获取默认程序域对象,如果在其他副AppDomain中,哪么就获取各自的AppDomain。
创建第一个例子。在项目MultiAppDomain,打开文件Program.cs ,在Main 函数中找到
AppDomain englishAppDomain = AppDomain.CreateDomain( " english " );
AppDomain bothAppDomain = AppDomain.CreateDomain( " both " );
AppDomain exceptionDomain = AppDomain.CreateDomain( " exception " );
- AppDomain之间值传递
只有继承了System.MarshalbyRefObject或者声明属性SerializableAttribute的类的实例,才能在AppDomain之间传递。即:
Public class A
{}
Public Class B:System.MarshalbyRefObject
{}
假设 A的实例a 和 B的实例b,需要从AppDomain X传递到AppDomain Y中。哪么 a实例相当于对象拷贝。在 AppDomain Y中任何对 a的操作都不会影响倒 AppDomainX中的a。而b是通过跨域的透明代理递到,相当于值引用。因此 在 AppDomain Y中,任何对b的操作对于对 AppDomain X中的b产生影响。参考下图:
在AppDoain中,可以通过AppDomain.SetData 和 GetData方法进行存取操作
AppDomain的动态导入
要实现在默认AppDomain中实现对副AppDomain的导入控制,必须获取副AppDomain中的RemotLoader的实例如下图
导入前,首先打开 MultiAppDomain项目中的RemoteLoader.cs 文件,查看一下Load方法。
{
private Dictionary < string , Assembly > assemblies = new Dictionary < string , Assembly > ();
public void Load( string fullPath)
{
byte [] fsContent;
using (FileStream fs = File.OpenRead(fullPath))
{
fsContent = new byte [fs.Length];
fs.Read(fsContent, 0 , fsContent.Length);
}
Assembly assembly = Assembly.Load(fsContent);
assemblies.Add(assembly.GetName().Name, assembly);
}
}
在Load方法内,使用byte导入Assembly,这样做可以防止Assembly文件独占。如果使用文件路径方式导入即Assembly.Load(filePath), 那么文件在程序运行的过程中是不能被更改的。
在这里说个题外话, 在写这个例子的开始时,我是使用路径导入方式,开始一直都很顺利。当实现BothSayHi中SayHi的时候,却抛出转换异常的错误,说引用的透明代理(__TransparentProxy)对象不能被转换成为ISay接口类型。但是把代码粘贴在Main方法中就正常。后来试过N种方法,包括改为抽象类、类等都还是不行。正在百思不得其解,无意中把load导入改为byte导入就正常了。
我个人认为,是因为在Main方法中,自身独占了Debug目录下的Interface.dll。而其他副AppDomain是直接获取Interface项目下的Dll。因此在Main方法中的调用是成功的,因为元数据的所属文件只有自己占用。但是在BothSayHi中的Interface.dll是和其他副AppDomain一起使用的。因此在BothSayHi中,在转换的时候,刚好需要获取Interface的元数据,而interface刚好被其他副AppDomain占用而无法读取元数据。因此获取不了元数据,只好抛出转换异常。这个只是我个人的想法,希望有高手指出问题的本质。
好了言归正传 然后RemoteLoader需要在副AppDomain中创建,并在默认AppDomain引用。
在项目MultiAppDomain,文件Program.cs ,在Main 函数中找到:
(RemoteLoader) chineseAppDomain.CreateInstanceAndUnwrap( " MultiAppDomain " , " MultiAppDomain.RemoteLoader " );
也可以写成:
(RemoteLoader) chineseAppDomain.CreateInstance ( " MultiAppDomain " , " MultiAppDomain.RemoteLoader " ).Unwrap;
如果使用第二种方法创建,那么返回的是System.Runtime.Remoting.ObjectHandle 对象。这个对象在Msdn的定义是:包装按值封送对象引用,从而使它们可以通过间接寻址返回。因此在默认AppDomain中并不需要它的相关元数据(因此无需导入相关Assembly),直到使用方法unwarrp. 默认AppDomain才开始导入相关的元数据。
Unwrap后对象是一个透明代理,再进行强制转换。获得RemoteLoader代理后,为每个副域导入相关Assembly,如下:
{
string chineseDll = @" ../../../SayHiChinese/bin/Debug/SayHiChinese.dll " ;
string interfaceDll = @" ../../../Interface/bin/Debug/Interface.dll " ;
// 省略代码
private static void Main( string [] args)
{
// 省略代码
chineseLoader.Load(interfaceDll);
chineseLoader.Load(chineseDll);
chineseLoader.CreateService( " sayHi " , " SayHiChinese " , " SayHiChinese.SayHi " );
// 省略代码
}
}
留意下chineseLoader.Load(interfaceDll),这句是可以省略的(开始不明原因,兴奋了好一阵子,以为.net有这么高的只能)。首先,在MultiAppDomain中所在的默认目录已经存在这个文件(因为项目显示引用了Interface项目),然后在创建chineseAppDomain的时候,我并没有指定副AppDomain的BaseDirectory,因此BaseDirecotry默认指向MultiAppDomain的运行目录。而在这MultiAppDomain项目中,我们引用了Interface,因此在路径中也包含了Interface.Dll,所以当我们调用chineseLoader.CreateService()创建SayHiChinese.SayHi实例时,CLR自动从MultiAppDomain目录中,自动导入Interface.Dll。
因为BothSayHi是需要其他两个实例的支持才能完成,因此创建代码就需要输入chinese和English的实例的
bothLoader.Load(bothDll);
bothLoader.SetServiceFrom( " english " , englishLoader.GetService( " sayHi " ));
bothLoader.CreateService( " sayHi " , " BothSayHi " , " BothSayHi.SayHi " );
在BothSayHi中实现调用chinese和english的服务:
bothSayHi的方法
{
ISayHi chinese = (ISayHi)AppDomain.CurrentDomain.GetData( " chinese " );
ISayHi english = (ISayHi) AppDomain.CurrentDomain.GetData( " english " );
Console.WriteLine(chinese.Say() + " " + english.Say());
return chinese.Say() + " " + english.Say();
}