使用.Net建立的可执行程序*.exe,并没有直接承载到进程当中,而是承载到应用程序域(AppDomain)当中。应用程序域是.Net引入的一个新概念,它比进程所占用的资源要少,可以被看做是一个轻量级的进程。一个应用程序域可以有多个线程,一个线程也可以穿梭于多个应用程序域。
在一个进程中可以包含多个应用程序域,一个应用程序域可以装在一个可执行程序(*.exe)或者多个程序集(*.dll)。这样可以使应用程序域之间实现深度隔离,即使进程中的某个应用程序域出现错误,也不会影响其他应用程序域的正常运作。
当一个程序集同时被多个应用程序域调用时,会出现两种情况:
- CLR分别为不同的应用程序域加载此程序集。
- CLR把此程序集加载到所有的应用程序域之外,并实现程序集共享,此情况比较特殊,被称作为Domain Neutarl。
应用程序域:(Application Domain,简称App Domain)一组程序集的一个逻辑容器,进程中的一个逻辑分区。通常由运行时宿主创建和操作。AppDomain唯一的作用就是进行隔离。
具体功能:
- 隔离,一个AppDomain中的代码创建的对象不能由另一个AppDomain中的代码直接访问。达到隔离应用程序的效果。当然如果要访问别的AppDomain中的内容,可以使用“按引用封送”或者“按值封送”的语义。
- AppDomain可以卸载,但不能卸载单独的程序集或类型,只能卸载整个应用程序域。从而卸载包含在该AppDomain中的所有程序集。
- AppDomain可以单独保护,AppDomain在创建后,会应用一个权限集,它决定了向这个AppDomain中运行的程序集授予的最大权限。从而保护宿主加载的代码不被破坏。
- 可以单独实施配置,AppDomain在创建后,会关联一组配置设置。这些设置主要影响CLR在AppDomain中加载程序集的方式。这些设置涉及搜索路径、版本重定向、卷影复制以及加载器优化。
适合使用AppDomain的条件:
- 需要隔离的程序集,譬如一些特别容易引起崩溃的代码可以考虑单独运行于一个特定的 appDomain;
- 不同安全级别的程序集,如果需要为自己的代码划分安全执行的边界,可以考虑将不同安全级别的代码单独创建于某个设定了不同安全信息的 appDomain
- 从性能上考虑,有些程序集可能会消耗大量资源,尽管在托管环境下,基本上不存在资源消耗漏洞,但是总会存在特定时间访问密集造成消耗大量资源的情况,这时可以考虑创建单独的 appDomain ,在资源消耗超过临界点后进行 appDomain 的卸载,适应系统运行要求。 Asp.net 中利用不同的appDomain 来提供支持就是为了防止一个应用程序的崩溃影响其他 asp.net 应用程序,同时 , 在不重新启动的系统不重新启动 IIS 不影响 asp.net 自身服务提供的情况下将一个 appDomain 卸掉同时启动新的 appDomain ,理想情况下可以实现 web 系统的长时间在线(这以往是昂贵的 unix 的特性,终于被 MS “借鉴”了)。
- 不同版本的同一应用程序集的同时运行。这个在 COM 时代是一个大问题,现在通过 appDomain ,实现了在一个进程中执行版本不同的两个程序集,可以做到良好的兼容性。
- 动态加载一些程序。
一、AppDomain的属性与方法
在System命名空间下就存在AppDomain类,用于管理应用程序域。
AppDomain的常用属性:
属性 | 说明 |
ActivationContext | 获取当前应用程序域的激活上下文。 |
ApplicationIdentity | 获得应用程序域中的应用程序标识。 |
BaseDirectory | 获取基目录。 |
CurrentDomain | 获取当前 Thread 的当前应用程序域。 |
Id | 获得一个整数,该整数唯一标识进程中的应用程序域。 |
RelativeSearchPath | 获取相对于基目录的路径,在此程序集冲突解决程序应探测专用程序集。 |
SetupInformation | 获取此实例的应用程序域配置信息。 |
AppDomain的常用方法:
方法 | 说明 |
CreateDomain | 创建新的应用程序域。 |
CreateInstance | 创建在指定程序集中定义的指定类型的新实例。 |
CreateInstanceFrom | 创建在指定程序集文件中定义的指定类型的新实例。 |
DoCallBack | 在另一个应用程序域中执行代码,该应用程序域由指定的委托标识。 |
ExecuteAssembly | 执行指定文件中包含的程序集。 |
ExecuteAssemblyByName | 执行程序集。 |
GetAssemblies | 获取已加载到此应用程序域的执行上下文中的程序集。 |
GetCurrentThreadId | 获取当前线程标识符。 |
GetData | 为指定名称获取存储在当前应用程序域中的值。 |
IsDefaultAppDomain | 返回一个值,指示应用程序域是否是进程的默认应用程序域。 |
SetData | 为应用程序域属性分配值。 |
Load | 将 Assembly 加载到此应用程序域中。 |
Unload | 卸载指定的应用程序域。 |
AppDomain事件:
事件 | 说明 |
AssemblyLoad | 在加载程序集时发生。 |
AssemblyResolve | 在对程序集的解析失败时发生。 |
DomainUnload | 在即将卸载 AppDomain 时发生。 |
ProcessExit | 当默认应用程序域的父进程存在时发生。 |
ReflectionOnlyAssemblyResolve | 当程序集的解析在只反射上下文中失败时发生。 |
ResourceResolve | 当资源解析因资源不是程序集中的有效链接资源或嵌入资源而失败时发生。 |
TypeResolve | 在对类型的解析失败时发生。 |
UnhandledException | 当某个异常未被捕获时出现。 |
二、在AppDomain中加载程序集
通过CreateDomain方法可以建立一个新的应用程序域。
下面给出一个使用CreateDomain建立一个应用程序域,并使用Load方法加载程序集Model.dll。
最后使用GetAssemblies方法,列举此应用程序域中的所有程序集。
public class Program { static void Main(string[] args) { var domain = AppDomain.CreateDomain("MyAppDomain"); domain.Load(@"控制台 - 学习测试"); foreach (var assembly in domain.GetAssemblies()) { Console.WriteLine(assembly.FullName); } Console.ReadKey(); } }
输出结果如下:
注意:当加载程序集后,就无法把它从AppDomain中卸载,只能把整个AppDomain卸载。
当需要在AppDomain加载可执行程序时,可以使用ExecuteAssembly方法。
如,建一个控制台程序:
class Program { static void Main(string[] args) { Console.WriteLine("供应用程序域执行!"); Console.ReadKey(); } }
将上面程序生成的路径保存到:C:\Users\ChenZhuo\Desktop\ConsoleApplication1\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe
下面我们创建一个应用程序域并执行上面那个程序集:
public class Program { static void Main(string[] args) { var domain = AppDomain.CreateDomain("MyAppDomain"); //执行指定的程序集 domain.ExecuteAssembly(@"C:\Users\ChenZhuo\Desktop\ConsoleApplication1\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe"); Console.ReadKey(); } }
输出结果如下:
三、卸载应用程序域
通过Unload可以卸载AppDomain,在AppDomain卸载时将会触发DomainUnload事件。
下面使用CreateDomain建立一个名为NewAppDomain的应用程序域。然后建立AssemblyLoad事件处理方法,在程序集加载时显示程序集的信息。最后建立DomainUnload事件处理方法,在AppDomain卸载时显示卸载信息。
class Program { static void Main(string[] args) { //新建一个应用程序域 AppDomain MyDomain = AppDomain.CreateDomain("domain"); //建立AssemblyLoad事件处理方法 MyDomain.AssemblyLoad += (sender, e) => { Console.WriteLine("程序集正在加载!" + e.LoadedAssembly.FullName); }; //建立AssemblyUnload事件处理方法 MyDomain.DomainUnload += (sender, e) => { Console.WriteLine("程序集正在卸载!"); }; //加载程序集 MyDomain.Load("MySpace"); Thread.Sleep(1000); Console.WriteLine("正在工作中!"); //卸载程序集 AppDomain.Unload(MyDomain); Console.ReadKey(); } }
输出结果如下:
四、在AppDomain中建立程序集中指定类的对象
使用CreateInstance方法,能建立程序集中指定类的对象。但使用此方法将返回一个ObjectHandle对象,若要将此值转换为原类型,可调用UnWrap方法。
下面例子利用MySpace.dll程序集中的MySpace.Person对象。
namespace ConsoleApplication1 { public class Program { static void Main(string[] args) { var person = (Person)AppDomain.CurrentDomain.CreateInstance("MySpace", "MySpace.Person").Unwrap(); person.Id = 1; person.Name = "张飞"; Console.WriteLine(person.Id + " : " + person.Name); Console.ReadKey(); } } }
Person:
namespace MySpace { public class Person { public int Id { get; set; } public string Name { get; set; } } }
输出结果如下: