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

C# 为什么事件处理程序阻止垃圾收集器发生,c#,events,garbage-collection,finalizer,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);

我有这段代码

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
    }
}
在我申请的主要方法中,我有

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());
}
static void Main(字符串[]args)
{
Publisher Publisher=新Publisher();
对于(int i=0;i<10;i++)
{
订户=新订户(发布者);
订户=null;
}
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine(Subscriber.Count.ToString());
}
如果我运行这个,我将有0作为输出。 如果我从代码中删除事件订阅,我将得到预期的结果,即10

调用GC.Collect()时,将强制GC启动垃圾收集。由于订阅服务器中定义了Finalize,GC将暂停收集,直到finalizeequeue为空–也就是说,所有订阅实例都将调用其Finalize()方法(如果我的假设错误,请纠正我)。在下一行中,调用了GC.WaitForPendingFinalizers(),它将有效地暂停执行,直到终结器队列为空为止。现在,因为我们有0作为输出,我相信没有调用Finalize(),这使我相信GC没有标记要收集的订阅服务器实例,因此没有调用Finalizer()方法

所以我有两个问题

  • 我的假设正确吗?事件订阅阻止GC标记要收集的订阅服务器实例吗
  • 如果是,那是因为publisher持有对subscriber的引用?()
  • 我唯一的猜测是,由于有10个订阅服务器实例引用同一个发布服务器实例,当GC收集发生时,它会看到还有其他对发布服务器的引用,因此无法收集,因此所有订阅服务器实例以及发布服务器都被移到下一代,因此,当代码执行到达控制台.WriteLine(Subscriber.Count.ToString())时,不会发生垃圾收集,也不会调用Finalize()


    我是对的还是遗漏了什么?

    这两个问题的答案都是肯定的。

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

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


    在此基础上展开一点,这里有循环引用。订阅服务器对象引用发布服务器对象。发布者对象具有对订阅者对象的引用。循环引用不足以使对象保持活动状态。谢天谢地,如果是这样的话,垃圾收集将不会非常有效。发布者对象必须在别处引用才能保持活动状态,局部变量不够好。

    这样考虑:当调用
    publisher.SomeEvent
    委托时,它将在调用时执行订阅给它的任何内容。这意味着订阅服务器实例必须在订阅处于活动状态时处于活动状态。这就是为什么Publisher必须保留对所有订阅者的引用。由于订阅服务器是可访问的,GC无法收集它们。感谢您的确认,您认为最后一段中的假设也正确吗?@michaelmoore订阅服务器没有对发布服务器的引用。这是
    Main
    中的局部变量
    publisher
    ,它阻止
    publisher
    实例以及10个
    Subscriber
    实例被收集。您还没有解释publisher未被收集的原因。它正在被收集,只是当OP试图运行他的代码时没有。谢谢你的回答!实际上,当我按ctrl+f5运行Release build时,输出是10。“订阅服务器对象引用发布服务器对象。”真的吗?我想这意味着只要继续引用发布服务器对象,就可以保证订阅服务器对象保持活动状态?当然,超过订阅服务器寿命的发布服务器不健康。顺便说一句,WPF的一个大问题是,他们提出了“弱事件模式”。这不是一个通用的解决方案,它会消耗大量的周期来发现死掉的订户。用户界面相关事件可以,因为用户界面以人的速度运行,毫秒。程序员使用IDisposable来取消订阅事件并不少见,这也不正确。