如何使用C#正确卸载AppDomain?

25

我有一个应用程序,它加载外部程序集,我无法控制这些程序集(类似于插件模型,其他人创建和开发的程序集由主应用程序使用)。 它通过为这些程序集创建新的AppDomain来加载它们,然后当使用完这些程序集时,主AppDomain会卸载它们。

目前,它以简单的方式卸载这些程序集:

try
{
    AppDomain.Unload(otherAssemblyDomain);
}
catch(Exception exception)
{
    // log exception
}

然而,有时在卸载过程中会发生异常,具体来说是CannotUnloadAppDomainException。据我所知,这可以预料到,因为子应用程序域中的线程无法被强制中止,因为仍在执行非托管代码或线程处于finally块中

当线程调用Unload时,目标 域会被标记为要卸载。专用线程尝试卸载 域,域中的所有线程都将被终止。如果线程没有 终止,例如因为它正在执行非托管代码,或者因为 它正在执行finally块,则 在一段时间后,将在最初调用Unload的线程中引发 CannotUnloadAppDomainException。如果无法中止的线程最终结束, 目标域不会被卸载。 因此,在.NET Framework版本中 2.0域不能保证卸载,因为可能无法终止执行线程。

我的担忧是,如果程序集未加载,则可能会导致内存泄漏。一种潜在的解决方案是,如果发生上述异常,则终止主应用程序进程本身,但我宁愿避免这种激烈的行动。

我还考虑过重复几次卸载调用。可能像这样使用一个受限制的循环:

try
{
    AppDomain.Unload(otherAssemblyDomain);
}
catch (CannotUnloadAppDomainException exception)
{
    // log exception
    var i = 0;
    while (i < 3)   // quit after three tries
    {
        Thread.Sleep(3000);     // wait a few secs before trying again...
        try
        {
            AppDomain.Unload(otherAssemblyDomain);
        }
        catch (Exception)
        {
            // log exception
            i++;
            continue;
        }
        break;
    }
}

这有意义吗?我是否应该再次尝试卸载?还是只尝试一次然后继续进行?还有,如果线程仍在运行,是否可以从主AppDomain执行某些操作来控制外部程序集(请记住其他人正在编写和运行此外部代码)?

我正在尝试了解管理多个AppDomains的最佳实践。


5
这个问题没有一个明确的答案。如果您无法控制应用程序中的线程,那么很难指望其中一个线程在3秒后突然合作。将其隔离到一个进程中是唯一真正的解决方法。 - Hans Passant
谢谢Hans。听起来这是AppDomains相对于进程的一种限制。我希望有一种像你建议的强制终止进程一样在任何情况下都可以强制终止AppDomain的方法。 - Ray
@HansPassant 把执行隔离到专用进程中真的可以确保避免这种情况吗? - lysergic-acid
3个回答

12

我在我的应用程序中遇到了类似的问题。基本上,你无法采取任何措施强制AppDomain关闭,超过Unload的作用范围。

它基本上会调用所有正在执行AppDomain代码的线程的中止操作,如果该代码被卡在终结器或非托管代码中,则无法做太多事情。

如果根据所涉及的程序,可以预计终结器/非托管代码将在以后某个时间完成,那么你绝对可以再次调用Unload。如果不是这样,你可以故意泄漏域或者循环该进程。


0

我遇到了类似的问题,随机行为已经持续了几个月了(有些app.Unload甚至在某些机器上永远阻塞!) 最终决定采取进程隔离。 您可以生成子控制台进程并重定向输出

如果需要取消,只需轻松杀死子进程和所有依赖项/句柄。

极端情况下,我不得不运行专用的清理代码,并想出了创建附加进程的解决方案,该进程具有等待从初始运行程序进程的控制台输出中提取的输入的专用cmd行。

是的,这个应用程序域真是一个笑话,我认为这不是它不再在net core中的巧合。


0
尝试在不卸载域的情况下执行GC.Collect()。
 try
    {
       AppDomain.Unload(otherAssemblyDomain);
    }
    catch (CannotUnloadAppDomainException)
    {
       GC.Collect();
       AppDomain.Unload(otherAssemblyDomain);
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接