【问题标题】:Autounsubscribe events自动取消订阅事件
【发布时间】:2012-11-23 12:10:00
【问题描述】:

我有一个发布一些事件的静态类。所以我有很多不同的小类,它们有不同的生命周期来订阅这个事件。

我现在发现这会导致内存“泄漏”,因为订阅类在订阅一个更长寿的事件时会保持活动状态。我知道这是通过使用内存分析器发生的,并且我读到了这个问题。

我无法手动取消订阅,因为我的列表中可能有数百个“客户”。此列表将被清除。所以我不能(也不想)手动取消订阅。

我读到“弱事件模式”在这里可能会有所帮助。有人可以引导我采用一种“简单”的方式来实现这一点吗?到目前为止,我发现要么太简单而无法在实践中使用,要么太复杂以至于一开始就无法理解。

或者这种情况有什么“最佳实践”吗?

提前致谢!

更新: 根据 jbl 的回答,我发现这个(http://blogs.msdn.com/b/greg_schechter/archive/2004/05/27/143605.aspx)是一种可能的解决方案。这里有cmets吗?它相当老了(2004 年),所以可能有更好的解决方案吗?

【问题讨论】:

  • 看看这个帖子,Fredric对你的问题的解释真的很有帮助,stackoverflow.com/questions/3662842/…
  • 你看过 WPF 中的WeakEventManager (msdn.microsoft.com/en-us/library/aa970850.aspx) 吗?
  • @Dennis:我目前正在使用 WinForms。所以我不是 100% 能够使用 WPF-Stuff。但我会阅读这个主题。谢谢!
  • @chiffre:好吧,WeakEventManager 的使用并不意味着“使用 WPF”,不需要渲染 WPF 控件。
  • @dennis:但我必须参考 WPF 程序集。我不会吗?

标签: c# events memory-leaks event-handling


【解决方案1】:

从未实现过类似的东西,但我会尝试(使用静态类或单例,您可以选择):

  • 让静态类维护对客户端事件处理程序的 WeakReference 的静态集合
  • 客户端不直接订阅该事件。静态类公开 subscribe 和 unsubscribe 方法,它们从弱引用集合中添加/删除处理程序
  • 静态类是唯一直接订阅事件的类
  • 在事件触发时,静态类枚举弱引用集合并为仍然存在的引用运行处理程序(删除空引用)

希望这会有所帮助

【讨论】:

  • 所以你需要一个 WeakEvent-Class 来处理每一种事件?以及如何确保调用/完成“取消订阅”?
  • 不,WeakReference 是您的处理程序的包装器(“不知何故”,就像将它包装到一个对象中)。 msdn.microsoft.com/en-us/library/system.weakreference.aspx 您无需确定已调用取消订阅,因为 WeakReference 不会阻止垃圾收集。当对应的对象被垃圾回收后,弱引用的内容将为空
【解决方案2】:

最佳实践:当类订阅由非此类构造的对象生成的事件时,始终实现Dispose 模式。

然后在 Dispose 方法中删除处理程序。

public NotificationServiceAccessor(ObjectWithEvent objectWithEvent)
{
    _notificationService = new NotificationService();
    _notificationService.StatusChanged += NotificationService_StatusChanged; // Local object, no Dipose

    _objectWithEvent = objectWithEvent;
    _objectWithEvent.AnEvent += AnEventHandler(); // Event that has to be disposed.
}

    #region IDisposable Members

    protected bool Disposed { get; private set; }

    private void Dispose(bool disposing)
    {
        if (!this.Disposed)
        {
            this.InternalDispose(disposing);
        }

        this.Disposed = true;
    }

    protected virtual void InternalDispose(bool disposing)
    {
        if (disposing)
        {
                        // Dispose here the event handlers
                        _objectWithEvent.AnEvent -= AnEventHandler()
        }

        // Dispose here only unmanaged objects 
        // Don’t use managed objects here because maybe 
        // they have been finalized already
    }

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~NotificationServiceAccessor()
    {
        this.Dispose(false);
    }

    #endregion

【讨论】:

  • 有任何示例实现吗?
  • @SoMoS:完全不同意,这是最佳实践。这只是一种方法,适用于某些情况。
  • 感谢您采用这种方法。但我认为这在“任何”情况下都不会帮助我。因为有可能订阅者的最后一个引用是“无效的”,但我不能 100% 确定调用 Dispose-Method。
  • @Dennis:好的,我一直在使用它,但如果您知道什么时候最好不要遵循此规则,请告诉我们。
  • @SoMoS:这种方法只允许在显式调用IDisposable.Dispose 时取消订阅事件。反过来,这假设您在某处保留对一次性对象的引用。有许多用例,当不可能或不适合时 - 保持对一次性对象的引用。例如,看看这个问题 - 这里我们有一个 static 事件,这意味着任何一段代码都可以在进程生命周期内订阅这些事件。您真的想要跟踪所有这些一次性用品吗?
猜你喜欢
  • 1970-01-01
  • 2018-01-05
  • 1970-01-01
  • 1970-01-01
  • 2017-04-25
  • 2016-02-28
  • 2019-02-11
  • 2021-02-25
  • 1970-01-01
相关资源
最近更新 更多