应用程序域,你真的了解吗?它可以干什么?
说到应用程序域Appdomain,我们必须要知道的知识就是进程,那么到底什么是进程呢?你不妨问问自己,怎么给进程下一个定义。到这里,我先卖个关子,你自己先想。
我们先来看一下win7中的进程:
有很多进程是吧,每一个进程都像是在执行者一个程序,或是监听或是执行或是空闲。这也是windows系统中的一个基本概念,它拥有一个运行着的程序所需要的系统资源比如分配内存。而且聪明的你一定试过,当你强行终止一个进程的时候,其他进程不会都跟着消亡,而且进程间是无法互相访问的(除非利用分布式计算方式,这都是后话了),windows系统呢,就是利用进程把工作划分为多个独立的区域的。这时候,上面的问题你应该有了答案了。对!进程就是一个程序的基本边界。
那么我们在.Net中是如何处理进程类的呢?
首先,我们要了解,System.Diagnostics这个命名空间,这里面存在一个Process类,它可以用来管理进程,比如Start,Kill,访问进程中的模块,获取进程中的线程,设定进程的优先级等。
下表显示Process常用属性。
BasePriority | 获取关联进程的基本优先级。 |
ExitCode | 获取关联进程终止时指定的值。 |
ExitTime | 获取关联进程退出的时间。 |
Handle | 返回关联进程的本机句柄。 |
HandleCount | 获取由进程打开的句柄数。 |
HasExited | 获取指示关联进程是否已终止的值。 |
Id | 获取关联进程的唯一标识符。 |
MachineName | 获取关联进程正在其上运行的计算机的名称。 |
MainModule | 获取关联进程的主模块。 |
Modules | 获取已由关联进程加载的模块。 |
PriorityClass | 获取或设置关联进程的总体优先级类别。 |
ProcessName | 获取该进程的名称。 |
StartInfo | 获取或设置要传递给Process的Start方法的属性。 |
StartTime | 获取关联进程启动的时间。 |
SynchronizingObject | 获取或设置用于封送由于进程退出事件而发出的事件处理程序调用的对象。 |
Threads | 获取在关联进程中运行的一组线程 |
除了上述属性,Process类也定义了下列常用的方法:
方法 | 说明 |
GetProcessById | 创建新的 Process 组件,并将其与您指定的现有进程资源关联。 |
GetProcessByName | 创建多个新的 Process 组件,并将其与您指定的现有进程资源关联。 |
GetCurrentProcess | 获取新的 Process 组件并将其与当前活动的进程关联。 |
GetProcesses | 获取本地计算机上正在运行的每一个进程列表。 |
Start | 启动一个进程。 |
Kill | 立即停止关联的进程。 |
Close | 释放与此组件关联的所有资源。 |
WaitForExit | 指示 Process 组件无限期地等待关联进程退出。 |
让我们写几行代码试一下
利用 Start 与Kill 方法可以简单建立或者销毁进程,下面例子就是利用 Start 方法启动记事本的进程,并打开Test.txt文件。2秒钟以后,再使用 Kill 方法销毁进程,并关闭记事本。
static void Main(string[] args) { Process process = Process.Start("notepad.exe", "Test.txt"); Thread.Sleep(2000); process.Kill(); Console.Read(); }
运行后打开一个记事本并且提示你是否新建Test.txt,2秒后关闭该记事本。
其他的,聪明的你可以自己去试验了。
那么上面说了一大堆,就是为了引出我们想要了解的---应用程序域,你可能会问,说了一大堆,为啥我一直在说进程?别急,慢慢往下看。
使用.Net建立的可执行程序.exe,并没有直接承载到进程中,而是承载到应用程序域(AppDomain)中了,为什么要这样呢,因为它比进程所占用的资源要少的多,我们甚至可以把它看成一个轻量级的进程。
那么就可以理解下面这段话了:在一个进程中,可以包含多个应用程序域,一个应用程序域可以装载多一个可执行程序.exe或者多个程序集.dll。这样可以使应用程序域之间实现深度隔离,即使进程中的某个应用程序域出现错误,也不会影响到其他应用程序域的正常操作。(千万不要和线程搞混了哟,区别还是蛮大的,你应该又可以理解它和线程之间的关系了),佐一张图以明理:
-----那么问题来了-------
当一个程序集同时被多个应用程序域调用时,会出现哪两种情况?:::::
---------------------------------------------------------- 第一种情况:CLR分别为不同的应用程序域加载此程序集。
-------------------------------------------------------------------------------------------- 第二种情况:CLR把此程序集加载到所有的应用程序域之外,并实现程序集共享,此情况比较特殊,被称作为Domain Neutral。(中立)
参照进程,再看看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 | 当某个异常未被捕获时出现。 |
实战一下------------------------------------------------------------------------------------------>
可以看到,通过CreateDomain方法可以建立一个新的应用程序域。 下面的例子将使用CreateDomain建立一个应用程序域,并使用Load方法加载程序集System.Web.Http.dll(我随便找来的一个,默认在bin文件夹里找,记住咯)。最后使用GetAssemblies方法,列举此应用程序域中的所有程序集。
static void Main(string[] args) { var appDomain = AppDomain.CreateDomain("Test"); appDomain.Load("System.Web.Http"); Parallel.ForEach(appDomain.GetAssemblies(), assembly => { Console.WriteLine(assembly.FullName); }); Console.Read(); }
结果:
但是要注意的是,一旦加载了程序集,是没法直接卸载它的,必须把承载的AppDomain整个卸载才行。
看实战
通过Unload可以卸载AppDomain,在AppDomain卸载时将会触发DomainUnload事件。
下面的例子中,将会使用CreateDomain建立一个名为NewAppDomain的应用程序域。然后建立AssemblyLoad的事件处理方法,在程序集加载时显示程序集的信息。最后建立DomainUnload事件处理方法,在AppDomain卸载时显示卸载信息。
static void Main(string[] args) { AppDomain newAppDomain = AppDomain.CreateDomain("NewAppDomain"); //建立AssemblyLoad事件处理方法 newAppDomain.AssemblyLoad += (obj, e) => { Console.WriteLine(string.Format("{0} is loading", e.LoadedAssembly.GetName())); }; //建立DomainUpload事件处理方法 newAppDomain.DomainUnload += (obj, e) => { Console.WriteLine("NewAppDomain Unload"); }; //加载程序集 newAppDomain.Load("ChnCharInfo"); //卸载AppDomain AppDomain.Unload(newAppDomain); Console.Read(); }
结果:
------------------------------------------------------------------------
为了加深印象,再来一例:建立程序集中指定类的对象
使用CreateInstance方法,能建立程序集中指定类的对像。但使用此方法将返回一个ObjectHandle对象,若要将此值转化为原类型,可调用Unwrap方法。
下面例子会建立Model.dll程序集中的Model.Person类。
static void Main(string[] args) { var person = (Person)AppDomain.CurrentDomain.CreateInstance("Model", "Model.Person").Unwrap(); person.ID = 1; person.Name = "test"; person.Age = 29; Console.WriteLine(string.Format("{0}‘s age is {1}",person.Name,person.Age)); Console.Read(); }
结果:
Model.dll
namespace Model { public class Person { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } } }
这时候你应该知道了什么是应用程序域,那么是不是就结束了呢?当然不是!你还应该知道一个更细粒度的.Net上下文。下面我们通过一句话来阐述三者的关系。
进程可以承载一组相关的.Net程序集,而应用程序域是对该进程的逻辑细分。一个应用程序域进一步被细分成多个上下文边界,这些边界用来分组目的相似的.Net对象。使用上下文的概念,CLR便能够确保恰当地控制那些带特殊运行时要求的对象。
那么上下文到底可以干什么:
1:上下文用来承载.Net对象的实体,所有的.Net对象都存在于Context中。
2:每个AppDomain当中至少存在一个默认Context(Context 0)
一般情况下, 不要要指定特定上下文的对象被称为上下文灵活对象(Context-agile),建立此对象不需要特定的操作,只需要由CLR自行管理,一般这些对象都会被建立在Context 0中。