为什么事件处理程序会阻止垃圾收集器的发生

Mic*_*ael 8 c# events garbage-collection finalizer

我有这段代码

public class Publisher
{
    public event EventHandler SomeEvent;
}

public class Subscriber
{
    public static int Count;

    public Subscriber(Publisher publisher)
    {
        publisher.SomeEvent += new EventHandler(publisher_SomeEvent);
    }

    ~Subscriber()
    {
        Subscriber.Count++;
    }

    private void publisher_SomeEvent(object sender, EventArgs e)
    {
        // TODO
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的应用程序的主要方法中我有

static void Main(string[] args)
{
    Publisher publisher = new Publisher();

    for (int i = 0; i < 10; i++)
    {
        Subscriber subscriber = new Subscriber(publisher);
        subscriber = null;
    }

    GC.Collect();
    GC.WaitForPendingFinalizers();

    Console.WriteLine(Subscriber.Count.ToString());
}
Run Code Online (Sandbox Code Playgroud)

如果我运行它,我将输出0.如果我从代码中删除事件订阅,我将得到期望的结果 - 这是10.

调用GC.Collect()时,gc被强制启动垃圾收集.因为订阅者已在其中定义了Finalize,GC将暂停收集,直到finalizequeue为空 - 即所有订阅实例将调用其Finalize()方法之后(如果我的假设错误,请纠正我).在下一行调用GC.WaitForPendingFinalizers(),它将有效地暂停执行,直到终结器队列为空.现在,因为我们输出0作为输出,我认为没有调用Finalize(),这使我相信GC没有标记要收集的订阅者实例,因此没有调用Finalizer()方法.

所以我有2个问题

  1. 我的假设是正确的,事件订阅会阻止GC标记要收集的订户实例吗?
  2. 如果是这样,那是因为出版商持有对订户的引用?(垃圾收集器和事件处理程序)

我唯一的猜测是,由于有10个订阅者实例正在引用同一个发布者实例,因此当GC收集发生时,它会看到有其他对发布者的引用,因此无法收集它,因此所有订阅与发布者一起的实例正被移动到下一代,因此在代码执行到达Console.WriteLine(Subscriber.Count.ToString())时不会发生垃圾收集,也不会调用Finalize()

我是对的还是我错过了什么?

Han*_*ant 4

您错误地识别了到底发生了什么,这是 C# 中非常常见的陷阱。您需要运行测试程序的发布版本,并在没有调试器的情况下运行它(按 Ctrl+F5)。它将在您的用户计算机上运行的方式。现在请注意,无论您是否订阅该活动,都不再重要了,您将始终获得 10 个。

问题是,当您使用调试器时,发布者对象不会被收集。我在这个答案中详细解释了原因。

稍微扩展一下,这里有循环引用。订阅者对象引用发布者对象。并且发布者对象具有对订阅者对象的引用。循环引用不足以使对象保持活动状态。谢天谢地,如果是这种情况,垃圾收集就不会很有效。发布者对象必须在其他地方引用才能保持活动状态,局部变量还不够好。