卸载AppDomain动态调用DLL异步线程执行失败

应用场景

动态调用DLL中的类,执行类的方法实现业务插件功能

使用Assembly 来实现

但是会出现逻辑线程数异常的问题

使用AppDomain 实现动态调用,并卸载。

发现问题某个插件中开启异步线程,会抛线程终止的异常。

百度查到把线程执行代码放到finally块中

 AppDomain很出色的一个能力就是它允许卸载。卸载AppDomain会导致CLR卸载AppDomain中的所有程序集,还会释放AppDomainLoader堆。为了卸载一个AppDomain,可以调用AppDomain的静态方法Unload。这导致了CLR执行一些列操作来得体地卸载指定AppDomain

1.         CLR挂起进程中执行过托管代码的所有线程

2.         CLR检查所有线程栈,查看哪些线程正在执行要卸载的那个AppDomain中的代码,或者哪些线程会在某个时刻返回至要卸载的那个AppDomain。在任何一个栈上,如果准备卸载的AppDomain,CLR都会强迫对应的线程抛出一个ThreadAbortException异常(同时恢复线程的执行)。这将导致线程展开(unwind),在展开的过程中执行遇到的所有finally块中的内容,以执行资源清理代码。如果没有代码捕捉ThreadAbortException,它最终会成为一个未处理的异常,CLR会“吞噬”这个异常,线程会终止,但进程可以继续运行。这一点是非常特别的,因为对于其他所有未处理的异常,CLR都会终止进程。

重要提示:如果一个线程当前正在finally块、catch块、类构造器、临界执行区(critical execution region)域或非托管代码中执行,那么CLR不会立即终止该线程。否则,资源清理代码、错误恢复代码、类型初始化代码、关键代码或者其他任何CLR不了解的代码都无法完成,导致应用程序的行为变得无法预测,甚至可能造成安全漏洞。线程在终止时,会等待这些代码块执行完毕。然后当代码块结束时,CLR再强制线程抛出一个ThreadAbortException

临界区是指线程终止或未处理异常的影响可能不限于当前任务的区域。相反,非临界区中的终止或失败只对出现错误的任务有影响。

3.         当上一步发现的所有线程都离开AppDomain后,CLR遍历堆,为引用了“已卸载的AppDomain创建的对象”的每一个代理都设置一个标志(flag)。这些代理对象现在知道它们引用的真实对象已经不在了。如果任何代码在一个无效的代理对象上调用一个方法,该方法会抛出一个AppDomainUnloadedException

4.         CLR强制垃圾回收,对现已卸载AppDomain创建的任何对象占用的内存进行回收。这些对象的Finalize方法被调用(如果存在Finalize方法),使对象有机会彻底清理它们占用的资源

5.         CLR恢复剩余所有线程的执行。调用AppDomain.Unload方法的线程将继续执行,对AppDomain.Unload的调用是同步进行的

在前面的例子中,所有工作都用一个线程来做。因此,任何时候只要调用AppDomain.Unload都不可能有另一个线程在要卸载的AppDomain中。因此,CLR不必抛出任何ThreadAbortException异常。

顺便说一句,当一个线程调用AppDomain.Unload方法时,针对要卸载的AppDomain中的线程,CLR会给它们10秒钟的时间离开。10秒后,如果调用AppDomain.Unload方法的线程还没有返回,CLR将抛出一个CannotUnloadAppDomainException异常,AppDomain将来可能会也可能不会卸载。

注意:如果调用AppDomain.Unload方法的线程不巧在要卸载的AppDomain中,CLR会创建另一个线程来尝试卸载AppDomain。第一个线程被强制抛出一个ThreadAbortException兵展开。新建的线程将等待AppDomain卸载,然后新线程会终止。如果AppDomain卸载失败,新线程将处理CannotUnloadAppDomainException异常。但是,由于我们没有写供这个新线程执行的代码,所以无法捕获这个异常。

原文地址:https://www.cnblogs.com/cwfsoft/p/6022222.html