No *_*ame 42 .net c# events dispose event-handling
我在C#工作,我的工作场所有一些代码标准.其中之一是我们连接的每个事件处理程序(例如KeyDown)必须在Dispose方法中断开连接.这有什么好的理由吗?
Jon*_*eet 74
除非您希望事件的发布者比订阅者活得更长,否则没有理由删除事件处理程序,没有.
这是民间传说成长的话题之一.你只需要按照正常的方式考虑它:发布者(例如按钮)有一个对订阅者的引用.如果同时发布者和用户将有资格在同一时间反正垃圾收集(常见),或者如果发行商将有资格进行垃圾回收较早,那么就没有GC的问题.
静态事件导致GC问题,因为它们实际上是一个无限长期的发布者 - 在可能的情况下,我会完全阻止静态事件.(我很少发现它们有用.)
另一个可能的问题是,如果您明确要停止侦听事件,因为如果引发事件,您的对象将出现异常(例如,它将尝试写入封闭的流).在这种情况下,是的,你应该删除处理程序.这很可能是在你的类IDisposable已经实现的情况下.它是不寻常的 - 虽然不是不可能 - 因为它IDisposable 只是为了删除事件处理程序而值得实现.
Jon*_*onP 11
如果我没有注销动态创建和销毁的用户控件的Dispose()中的事件处理程序,那么我的应用程序中会出现主要的GDI泄漏.我在C#编程指南的Visual Studio 2013帮助中找到了以下内容.注意我用斜体字放的东西:
如何:订阅和取消订阅活动
...略... 退订 要防止在引发事件时调用事件处理程序,请取消订阅该事件.为了防止资源泄漏,您应该在处置订阅者对象之前取消订阅事件.在取消订阅事件之前,发布对象中作为事件基础的多播委托具有对封装订阅者事件处理程序的委托的引用.只要发布对象保存该引用,垃圾回收就不会删除您的订阅者对象.
请注意,在我的情况下,发布者和订阅者都在同一个类中,并且处理程序不是静态的.
Emr*_*ain 11
好吧,也许,该标准是作为防止内存泄漏的一种防御措施而提出的。我不能说,这是一个糟糕的标准。但是,我个人更喜欢仅在需要时断开事件处理程序。通过这种方式,我的代码看起来干净且不那么冗长。
我写了一篇博客解释事件处理程序如何导致内存泄漏以及何时断开连接;https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16。在这里,我将总结解决您的核心问题的解释。
C# Event Handler 操作符实际上是一个引用注入器:
在 C# 中,+= 运算符看起来很无辜,许多新开发人员没有意识到右侧对象实际上传递的是对左侧对象的引用。
事件发布者保护事件订阅者:
那么,如果一个对象获得了对另一个对象的引用,那么有什么问题呢?问题是,当垃圾收集器开始清理并找到一个重要的对象保留在内存中时,它不会清理所有也被该重要对象引用的对象。让我说得简单点。假设您有一个名为“Customer”的对象。比如说,这个客户对象有一个对 CustomerRepository 对象的引用,这样客户对象就可以在存储库中搜索它的所有地址对象。因此,如果垃圾收集器发现客户对象需要处于活动状态,那么垃圾收集器也会使客户存储库保持活动状态,因为客户对象具有对 customerRepository 对象的引用。这是有道理的,因为客户对象需要 customRepository 对象才能运行。
但是,事件发布者对象是否需要事件处理程序才能运行?没有权利?事件发布者独立于事件订阅者。事件发布者不应该关心事件订阅者是否还活着。当您使用 += 运算符订阅事件发布者的事件时,事件发布者会收到事件订阅者的引用。垃圾收集器认为,事件发布者需要事件订阅者对象才能发挥作用,所以不收集事件订阅者对象。
这样,事件发布者对象“a”保护事件订阅者对象“b”不被垃圾收集器收集。
只要事件发布者对象还活着,事件发布者对象就会保护事件订阅者对象。
所以,如果你分离事件处理程序,那么事件发布者不持有事件订阅者的引用,垃圾收集器可以自由地收集事件订阅者。
但是,您真的需要一直分离事件处理程序吗?答案是否定的。因为只要事件发布者还活着,许多事件订阅者就应该一直存在于内存中。
做出正确决定的流程图:
大多数时候,我们发现事件订阅者对象与事件发布者对象一样重要,并且两者应该同时存在。
您无需担心的场景示例:
例如,一个窗口的按钮点击事件。
这里,事件发布者是 Button,事件订阅者是 MainWindow。应用该流程图,问一个问题,主窗口(事件订阅者)是否应该在按钮(事件发布者)之前死亡?显然不是。对吧?这甚至没有意义。那么,为什么要担心分离点击事件处理程序呢?
事件处理程序分离的示例必须:
我将提供一个示例,其中订阅者对象应该在发布者对象之前死亡。假设您的 MainWindow 发布了一个名为“SomethingHappened”的事件,您可以通过单击按钮从主窗口显示一个子窗口。子窗口订阅主窗口的那个事件。
并且,子窗口订阅主窗口的事件。
当用户单击 MainWindow 中的按钮时,会显示子窗口。然后当他/她从子窗口完成任务时,用户关闭子窗口。现在,根据我提供的流程图,如果你问一个问题“子窗口(事件订阅者)是否应该在事件发布者(主窗口)之前死亡?答案应该是肯定的。对吗?然后,确保分离子窗口的任务完成时的事件处理程序。一个很好的地方是 ChildWindow 的 Unloaded 事件。
验证内存泄漏的概念:
我使用 Jet Brains 的 dotMemory Memory profiler 软件分析了这段代码。我启动了 MainWindow 并单击了按钮3 次,它显示了一个子窗口。因此,出现了 3 个子窗口实例。然后,我关闭了所有子窗口并比较了子窗口出现前后的快照。我发现即使我已经关闭了所有的孩子窗口的3个对象仍然存在于内存中。
然后,我在子窗口的 Unloaded 事件中分离了事件处理程序,如下所示:
然后,我再次进行了分析,这一次,哇!该事件处理程序不再导致内存泄漏。
| 归档时间: |
|
| 查看次数: |
17601 次 |
| 最近记录: |