14

根据我所有的阅读,应该有一个 GC 线程来调用所有终结器。现在,问题是这个“一个”线程的范围是什么——每个进程或每个应用程序域,因为域的全部意图是在一个进程空间中分离并制作“独立”的不同应用程序。

我在这里读到:

如果在终结器中发生未处理的异常,CLR 的执行线程将吞下该异常,将终结器视为正常完成,将其从易碎队列中删除并移至下一个条目。

更严重的是,如果你的终结器由于某种原因没有退出会发生什么,例如它阻塞,等待一个永远不会发生的条件。在这种情况下,终结器线程将被挂起,因此不再有可终结的对象被垃圾收集。您应该非常了解这种情况,并坚持编写最简单的代码来释放终结器中的非托管资源。

另一个考虑因素是应用程序关闭期间会发生什么。当程序关闭时,垃圾收集器将努力调用所有可终结对象的终结器,但有一定的限制:

  • 在关闭期间,可终结对象不会提升到更高的堆代。

  • 任何单独的终结器将有最多 2 秒的时间来执行;如果需要更长的时间,它将被杀死。

  • 所有终结器的执行时间最长为 40 秒;如果此时任何终结器仍在执行或未决,则整个过程会突然终止。

太多帖子(甚至官方文档)滥用术语“应用程序”、“进程”和“应用程序域”——其中大多数甚至假设它们是相等的,因为通常应用程序在单个进程中的单个应用程序域中运行. 这种滥用使所有这些文档都难以阅读,甚至没有用处。

因此,我的问题假设有多个应用程序,每个应用程序都在单个进程的单独应用程序域中运行。

所有这些应用程序是否共享相同的 GC 和终结器线程?上面文章中描述的问题(挂起终结器线程)会影响该进程中的所有应用程序吗?如果是 - 是否有解决方法(除了不使用不良应用程序),例如以某种方式发现终结器线程并将其发送 Thread.Abort?

以上都是因为我遇到了类似的问题。我的应用程序作为第三方软件 (Outlook) 的插件在单独的应用程序域中运行。由于各种原因,我需要调用 GC.Collect 和 GC.WaitForPendingFinalizers 来完全释放 COM 引用(通常的互操作例程对于 Office/Outlook 来说是不够的),当某个特定的其他第三方插件运行时,我的 GC.WaitForPendingFinalizers 永远挂起,所以我怀疑第三方添加中有一个“坏”的终结器。我无法控制替换/删除添加(客户的要求),所以我必须自己弄清楚如何使它们共存。

4

1 回答 1

8

看起来它确实只是进程中每个 CLR 实例的一个线程——无论如何,目前。这里有一些代码可以证明:

测试.cs:

using System;

class Test
{
    static void Main()
    {
        AppDomain.CreateDomain("First")
                 .ExecuteAssembly("ShowFinalizerThread.exe");
        AppDomain.CreateDomain("Second")
                 .ExecuteAssembly("ShowFinalizerThread.exe");
    }
}

ShowFinalizerThread.cs:

using System;
using System.Threading;

class ShowFinalizerThread
{
    static Random rng = new Random();

    ~ShowFinalizerThread()
    {
        Console.WriteLine("Thread/domain: {0}/{1}",
                          Thread.CurrentThread.ManagedThreadId,
                          AppDomain.CurrentDomain.FriendlyName);
        if (rng.Next(10) == 0)
        {
            Console.WriteLine("Hanging!");
            Thread.Sleep(2000);
        }
    }

    static void Main()
    {
        new Thread(LoopForever).Start();
    }

    static void LoopForever()
    {
        while (true)
        {
            new ShowFinalizerThread();
            GC.Collect();
            GC.WaitForPendingFinalizers();
            Thread.Sleep(300);
        };
    }
}

将每个编译为控制台应用程序,然后运行 ​​test.exe(从命令行最简单,IMO)。您会看到一个应用程序域的终结器阻止了另一个。

将来我不会惊讶地看到每个核心而不是每个 AppDomain 有一个终结器线程 - 但听起来你仍然会遇到问题:(

你有我最深切的同情(虽然不是一个解决方案) - 一旦我在一个 Oracle Blob 中找到一个死锁。我们能够通过妥善处理它来解决这个问题,但我知道并非所有东西都能很好地工作——即使找到那个也真的很痛苦!

于 2008-10-27T23:02:01.830 回答