.NET AppDomain.Unload 触发失控线程

Posted

技术标签:

【中文标题】.NET AppDomain.Unload 触发失控线程【英文标题】:.NET AppDomain.Unload triggers runaway threads 【发布时间】:2013-10-10 11:08:14 【问题描述】:

我有一个 .NET 3.5 SP1 应用程序,它是一个 Excel 插件。该应用程序分为父域AppDomain(Excel 的)和一个子域,我们在其中加载所有 dll。当我们希望更新我们的应用程序时,我们会卸载子域,替换文件并重新加载它。

不幸的是,卸载域将激活 2 个工作线程,它们将开始消耗 CPU 周期 (20-40%)。

如果我使用 VS 2010 进行调试,在 AppDomain.Unload 之前和之后的那一刻,除了 Excel 的主线程之外,没有任何线程在调用堆栈中处于活动状态。 AppDomain.Unload 确实已卸载,因为如果我再次尝试调用 Unload,我会得到一个 AppDomainUnloadedException

如果我使用 ProcessExplorer,我可以看到 2 个线程正忙于工作,即使 VS 调试器已经中断。查看调用堆栈什么也没有发现,因为没有符号。

ntkrnlpa.exe+0x6eacb ntkrnlpa.exe+0x2bfd0 hal.dll+0x2ef2 ntkrnlpa.exe+0x6a6cf ntdll.dll+0xe514 mscorwks.dll+0x992d mscorwks.dll+0x52568 mscorwks.dll+0x15b469 kernel32.dll+0xb729

如果我使用 WinDbg,我可以看到 2 个叛变线程的调用堆栈。总是一样的:

警告:堆栈展开信息不可用。以下帧可能有误。 ntdll!KiFastSystemCallRet mscorwks+0x992d mscorwks!InstallCustomModule+0x1eca0 mscorwks!CorExitProcess+0x503b kernel32!GetModuleFileNameA+0x1ba

我创建了一个非常简单的测试应用程序来加载/卸载子程序集。使用简单的 1 类程序集执行此操作时,它可以正常工作。如果我让它加载/卸载真实应用程序的子域,它会触发相同的叛徒线程。

创建子域的代码如下:

AppDomainSetup appSetup = new AppDomainSetup();
appSetup.ApplicationBase = baseDir;

var ps = new PermissionSet(System.Security.Permissions.PermissionState.Unrestricted);
return AppDomain.CreateDomain(name, null, appSetup, ps, null);

从父域到子域的通信是通过代理和反射。创建它的代码如下:

string assName = typeof(ApplicationProxy).Assembly.FullName;
string className = typeof(ApplicationProxy).FullName;

var obj = _childDomain.CreateInstanceAndUnwrap(assName, className, false, 
    System.Reflection.BindingFlags.Default,
    null, new object[]_sessionGuid, 
    CultureInfo.InvariantCulture,
    null, new Evidence(AppDomain.CurrentDomain.Evidence));

_proxy = (ApplicationProxy)obj;

我已经大量搜索了这个问题,但找不到任何有类似问题的人。该应用程序有 10 个项目,因此我无法发布。

我想知道是否有人遇到过类似的事情并给我一些提示。否则有人对如何解决这个问题有任何想法吗?

【问题讨论】:

您的堆栈跟踪不足以诊断它们,您必须启用 Microsoft 符号服务器以获取这些 DLL 的 PDB。工具 + 选项、调试、符号。否则这是正常的,卸载 AppDomain 会使两个线程运行。一种是中止正在 AppDomain 中执行代码的线程和要清理的终结器线程。您的线程必须具有警报才能允许中止工作,将卸载与这些工作线程联锁很重要。 【参考方案1】:

感谢 Hans 让我走上了正确的道路。

有几个类带有终结器,所以我在每个类中都设置了一个断点。在其中之一中,有人调用 ThreadPool.QueueUserWorkItem。工作项永远不会被调用,而是让这 2 个线程(1 个用于中止执行线程,1 个用于完成任务)永远循环。

我在我的测试项目中测试过,确实如此。

孩子们,教训是不要让你的经理写线程代码。

【讨论】:

以上是关于.NET AppDomain.Unload 触发失控线程的主要内容,如果未能解决你的问题,请参考以下文章

AppDomain.Unload() 如何中止线程?

AppDomain.Unload 抛出终结器?

进程和 AppDomain 加载/卸载

即使我卸载 appdomain,我的 dll 也不会卸载

assembly 需要 unload 和 update 的时候怎么办?

C# 动态加载/卸载程序集