AppDomain很出色的一个能力就是它允许卸载。卸载AppDomain会导致CLR卸载AppDomain中的所有程序集。还会释放AppDomain的Loader堆。为了卸载一个AppDomain,可调用AppDomain的静态方法Unload。这将导致CLR执行一系列操作来得体的卸载指定AppDomain。
- CLR挂起进程中执行过托管代码的所有线程。
- CLR检查所有线程栈,查看那些线程正在执行要卸载的那个AppDomain中的代码,或者那些线程会在某个时候返回至要卸载的那个AppDomain。在任何一个栈上,如果有准备卸载的AppDomain,CLR会强迫对应的线程抛出一个ThreadAbortException异常(同时恢复线程的执行)。这将导致线程展开,在展开的过程中执行遇到的所有finally块中的内容,以执行资源清理代码。如果没有代码捕捉ThreadAbortException,它最终会成为一个未处理的异常,CLR会“吃掉”这个异常;
线程会终止,但进程可继续运行。这一点是非常特别的,因为对于其他所有未处理的异常,CLR都会终止进程。
- 当第2步发现的所有线程都离开AppDomain后,CLR遍历堆,为引用了”由已卸载的APPDomain创建的对象”的每一个代理对象都设置一个标志。这些代理对象现在知道他们引用的真实对象已经不在了。如果任何代码在一个无效的代理对象上调用一个方法,该方法会抛出一个AppDomainUnloadedException。
- CLR强制垃圾回收,对现已卸载AppDomain创建的任何对象占有的内存进行回收。这些对象的Finalize方法被调用,使对象有机会彻底清理他们所占用的资源。
- CLR恢复剩余所有线程的执行。调用AppDomain.Unload方法的线程将继续运行;对AppDomain.Unload的调用时同步进行的。
在Ch22-1-AppDomain应用程序中,所有工作都有一个线程来做。因此,任何时候只要调用AppDomain.Unload,都不可能有另一个线程在要卸载的AppDomain中。因此,CLR不必抛出任何ThreadAbortException异常。
顺便说一句,当一个线程调用AppDomain.Unload方法时,针对要卸载的AppDomain中的线程,CLR会给他们10秒钟的时间离开。10秒钟之后,如果调用AppDomain.Unload方法的线程还没有返回,CLR将抛出一个CannotUnloadAppDomainException异常,AppDomain将来可能会、也可能不会卸载。