为什么 GC 无法收集绑定到事件的实例

本文关键字:事件 实例 绑定 GC 为什么 | 更新日期: 2023-09-27 18:36:50

这个问题基于这个问题:如何释放包含事件的局部变量

有人说:一个事件处理程序到一个实例,就会增加实例的引用计数?为什么?

为什么 GC 无法收集绑定到事件的实例

正如Jon Skeet所说,你的链接问题是倒退的。唯一让watcher保持活力的东西可能是它自己的内部实现,它可能会(或可能不会)使用负责反馈"ticks"的较低级别的对象注册事件。-但这只是猜想。

单击该按钮两次将生成两个单独的watcher实例,每个实例都有一个PositionChanged事件的订阅者(恰好是同一实例上的相同方法)。

重要的是,在方法退出后,PositionChanged订阅者watcher保持活动状态的不是 - 而是另一回事(我怀疑隐藏在GeoCoordinateWatcher实现中)。当该方法退出时,对 watcher 的特定实例的引用会正确地从堆栈中弹出,但由于另一个引用保留在 watcher CLR 眼中的有效引用计数仍大于零 - 因此,不符合垃圾回收的条件。

因此,它将继续触发PositionChanged事件。由于事件中没有任何内容阻止观察程序继续,我猜您可能有内存泄漏,因为每次单击按钮都会创建并保留一个观察程序实例。

您要么只需要存储和使用一个GeoCoordinateWatcher类,要么在每次处理事件时关闭/处置/停止它。


事件和订阅的通常注意事项是注意订阅长期对象的短期对象。

委托在类的特定实例中保存对特定方法的引用(如果它是静态方法,则仅保存类型本身)。订阅事件会导致事件发布者无意中通过订阅的委托保存对订阅者实例的引用。

显然,如果将静态方法注册为事件处理程序,则不会获得此引用计数,因为没有实例。

如果不取消订阅,则

订阅服务器生存期较短且事件发布者生存期较长,则可能会发生内存泄漏。假设订阅者希望符合 GC 的条件,因为它对某处的事件具有活动订阅,并且对象仍然存在,因此在从该订阅列表中删除之前,它不符合条件。

通过调用 GeoCoordinateWatcher.Start 开始一个新任务。即使它像 local 变量一样在代码中声明,它也会在函数范围退出后继续存在。

你可以考虑一下,就像你从函数开始第 3 部分process一样。函数的范围消失了,但该过程仍然存在。

在提供的链接中,如果单击按钮两次,这将导致GeoCoordinateWatcher的 2 个不同实例由同一事件处理程序处理。 因此,该事件处理程序将从 2 个不同的 GeoCoordinateWatcher 实例中调用两次。

创建委托对象时,它包含MethodTarget属性。Target 属性指向将在其中调用Target方法的上下文的对象(第一个参数,也称为 this )。

在某些情况下,对委托对象的引用将处于活动状态,从而防止Target实例被 GC 化。它通常发生在您有一个基于插件/addin 的应用程序,或其他类型的后期绑定情况,或者您正在使用委托做大量工作并且委托对象存储在某个集合中,或者您有一个带有委托对象的静态字段,(因为静态字段永远不会被收集)等。

请记住,垃圾回收不容易受到"循环引用"问题的影响;对于被视为"有用"的对象,它必须可以从当前堆栈访问。