【问题标题】:Is there a clear way of identifying which event subscription causes memory leak and which doesn't?是否有明确的方法来识别哪个事件订阅会导致内存泄漏,哪个不会?
【发布时间】:2016-09-26 17:27:09
【问题描述】:

我非常了解事件订阅和取消订阅的语法。

myEvent += myEventHandler;  // subscribe
myEvent -= myEventHandler;  // unsubscribe

我喜欢事件订阅的 lambda 语法(自身包含在一个块中),但如果我需要取消订阅(以避免内存泄漏),我不能使用此语法。

因此,我的问题是,哪些事件需要取消订阅,哪些事件可以忽略以由 GC 处理? 例如,我将以下内容用于 UWP 应用,它们是否需要取消订阅,如果需要,为什么?

  • View 页面的 PointerMoved 事件 (Windows.UI.Xaml ns)(在代码隐藏中)
  • 来自与订阅页面相同命名空间中的另一个页面的事件。带有事件的源页面作为菜单容器保留,而订阅页面在用户控制下导航。

【问题讨论】:

  • 我没有标记它,但这似乎与this post 重复。当发布者的生存时间比订阅者长得多时,就会发生内存泄漏,因此如果您的发布者的范围是这样的,那么您可以“让 GC 处理取消订阅”,以便在所有订阅者也都超出范围后不久它就会消失 - 正如 Jon Skeet 所说在那个链接中,“通常我发现发布者和订阅者的生命周期大致相同”。你说,“但如果我需要取消订阅,我不能使用这种语法”——为什么不呢?
  • @Quantic - 回答您的最后一个问题:使用 lambda 语法,我假设无法取消订阅,但我可能是错的。如果您知道使用 lambda 语法取消订阅事件的语法,我将不胜感激。
  • 感谢@Quantic 的指点——是的,我可以看到一个副本。
  • 哦,我对其他语法感到困惑。如果您保留对委托的引用,则有一种方法,如 here 所示。

标签: c# events memory-leaks windows-runtime uwp


【解决方案1】:

C# 事件是该语言中考虑最少的功能之一。我的建议是:永远不要使用它们。如果你不小心添加了一个已经添加的事件处理程序,默认实现实际上会添加两次处理程序。这是一个很难检测和排除故障的错误。此外,如果您不小心删除了从未添加的事件处理程序,您不会收到任何错误:它会静默失败。本质上,它是在欺骗你:它在告诉你“是的,完成了!”,欺骗你相信你的三段论是正确的,而实际上它是错误的。这不是开发软件的方法。我知道全世界有很多人都在开发这样的软件,他们不知何故凑合了,我只能说我为他们感到难过。不要这样对自己。

实现你自己的观察者/可观察模式,断言没有添加两次,没有第一次添加就没有删除。在可观察对象的生命周期结束时,断言所有观察者列表都是空的,这意味着所有观察者都费心取消注册。是的,垃圾处理本来就是灵丹妙药,可以让我们不必担心类似的事情,但是,你猜怎么着,它没有。在纸上检查琐碎的例子和学术练习时,一切看起来都很好,但一旦事情开始变得有点复杂,让事情靠魔法来处理就行不通了。

一旦您发现某个观察者永远不会从可观察对象中注销,您需要找出观察者被分配的位置,和/或它被注册的位置。您可以通过获取当前堆栈跟踪并将其存储在观察者中来做到这一点,这样如果后来发现观察者还活着,您可以转储其堆栈跟踪。获取堆栈跟踪的方法如下:StackTrace stackTrace = new StackTrace();注意,这非常慢,(微软只知道为什么,)所以只有在实际需要时才使用它,即在对您知道的特定类进行故障排除时使用一个错误。修复错误后立即将其删除。

【讨论】:

  • 有趣的角度,感谢您抽出宝贵的时间。那么,您将如何实现对 PointerMoved 或类似事件的事件订阅呢?我总是乐于接受更好的选择。
  • 所有事件处理程序都可以表示为Action 代表,接受一个、两个等参数,假设最多四个参数。因此,您需要做的就是编写四个不同的通用事件管理器,每个事件管理器对应不同数量的参数。如果你很聪明,你可以将它们所有的通用功能提取到一个通用的基类中。或者,如果您不介意使用一些“魔法”,您可以编写一个通用事件管理器,将事件传递到任何接口:blog.michael.gr/2011/10/intertwine-normalizing-interface.html
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-01-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-11
  • 1970-01-01
相关资源
最近更新 更多