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()方法
所以我有两个问题
我是对的还是遗漏了什么?这两个问题的答案都是肯定的。你误认了真正发生的事情,这是C#中一个非常常见的陷阱。您需要运行测试程序的发布版本,并在没有调试器的情况下运行它(按Ctrl+F5)。它在用户机器上运行的方式。现在请注意,无论你是否订阅该活动,你都将获得10分 问题是,当您使用调试器时,不会收集发布者对象。我详细解释了原因
在此基础上展开一点,这里有循环引用。订阅服务器对象引用发布服务器对象。发布者对象具有对订阅者对象的引用。循环引用不足以使对象保持活动状态。谢天谢地,如果是这样的话,垃圾收集将不会非常有效。发布者对象必须在别处引用才能保持活动状态,局部变量不够好。这样考虑:当调用
publisher.SomeEvent
委托时,它将在调用时执行订阅给它的任何内容。这意味着订阅服务器实例必须在订阅处于活动状态时处于活动状态。这就是为什么Publisher必须保留对所有订阅者的引用。由于订阅服务器是可访问的,GC无法收集它们。感谢您的确认,您认为最后一段中的假设也正确吗?@michaelmoore订阅服务器没有对发布服务器的引用。这是Main
中的局部变量publisher
,它阻止publisher
实例以及10个Subscriber
实例被收集。您还没有解释publisher未被收集的原因。它正在被收集,只是当OP试图运行他的代码时没有。谢谢你的回答!实际上,当我按ctrl+f5运行Release build时,输出是10。“订阅服务器对象引用发布服务器对象。”真的吗?我想这意味着只要继续引用发布服务器对象,就可以保证订阅服务器对象保持活动状态?当然,超过订阅服务器寿命的发布服务器不健康。顺便说一句,WPF的一个大问题是,他们提出了“弱事件模式”。这不是一个通用的解决方案,它会消耗大量的周期来发现死掉的订户。用户界面相关事件可以,因为用户界面以人的速度运行,毫秒。程序员使用IDisposable来取消订阅事件并不少见,这也不正确。