我有这段代码
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() 方法未被调用。
所以我有两个问题
- 我的假设是否正确,事件订阅是否会阻止 GC 将订阅者实例标记为要收集?
- 如果是,那是因为发布者持有对订阅者的引用? ( 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/