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

标签 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
    }
}

在我的应用程序的 Main 方法中有

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());
}

如果我运行它,我将得到 0 作为输出。 如果我从代码中删除事件订阅,我将得到预期的结果——即 10。

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

所以我有两个问题

  1. 我的假设是否正确,事件订阅是否会阻止 GC 将订阅者实例标记为要收集?
  2. 如果是,那是因为发布者持有对订阅者的引用? ( Garbage collector and event handlers )

我唯一的猜测是,由于有 10 个 Subscriber 实例引用同一个 publisher 实例,当 GC 收集发生时,它看到还有其他对 publisher 的引用,因此无法收集,并且作为结果所有订阅实例和发布者都被移动到下一代,所以垃圾收集不会发生,也不会在代码执行到达 Console.WriteLine( Subscriber.Count.ToString())

我说得对还是漏掉了什么?

最佳答案

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

问题是,当您使用调试器时,不会收集publisher 对象。我在 this answer 中详细解释了原因.

再扩展一点,这里有循环引用。订阅者对象引用发布者对象。 Publisher 对象引用了 Subscriber 对象。循环引用不足以足以使对象保持事件状态。谢天谢地,如果是这样的话,垃圾收集将不会非常有效。发布者对象必须在别处被引用以保持事件状态,局部变量不够好。

关于c# - 为什么事件处理程序会阻止垃圾收集器的发生,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/17164566/

相关文章:

c++ - 单个 SetEvent() 可以触发多个 WaitForSingleObject()

JavaScript 窗口按比例调整大小

java - 如何在不保持 View 存活的情况下实现 MVC 模式

c# - 如何将对象转换为元组?

c# - 如何将任意 json 对象发布到 webapi

c# - 如果抛出异常,使 extern C++ 函数返回一条消息

c# - 如何使用azure sql进行多接口(interface)框架

c# - 从后台工作程序或事件更新GUI

objective-c - 如果我依赖 Objective-C 中的垃圾收集,我是否需要 getter 和 setter

c# - 将参数传递到 Task.Factory.StartNew