C# 中简单有效的弱事件调度器





4.00/5 (5投票s)
C# 中另一个弱事件调度器。
引言
一段时间前,我需要注册大量的事件处理程序。那时,我意识到如果不注销不需要的事件处理程序,就会发生内存泄漏。原因是此类处理程序的实例会持续被相应的事件调度器引用,从而阻止垃圾回收器进行垃圾回收。
另一个相关的问题是不需要的处理程序会持续被执行,因为它们的实例无法被垃圾回收,从而导致性能下降。因此,如果有可能,注销事件是一件重要的事情。
但当然,不用担心注销事件会很方便。
我发现这是一个相当常见的问题,并且在网上找到了许多解决方案和一些关于 WeakReference
和 WeakEventManager
类的优秀文章。这些解决方案在某种程度上对我有效,因为它们处理了内存泄漏问题。但是,对于每个解决方案,我遇到了性能或可用性问题,所以我决定编写一个简单的弱事件调度器,重点关注可用性和性能。
背景
弱引用是指在垃圾回收器需要决定是否对相应实例进行垃圾回收时,不会被垃圾回收器计算在内的引用。这意味着弱引用可能会在某个时刻突然变为 null,因为它在没有其他强引用指向该特定实例时被垃圾回收。这也意味着在使用弱引用时需要小心。
使用代码
这个 WeakEventDispatcher
在外部表现得像一个“正常”的事件调度器,也就是说,您可以轻松地添加和删除处理程序。在内部,处理程序与指向其实例的弱引用一起被分桶。调用是通过编译的 lambda 表达式(即委托)完成的。这些委托被缓存并在可能的情况下被重用。清除 (摆脱属于垃圾回收实例的处理程序) 定期进行,可以通过阈值设置进行配置。因此,不再存在内存泄漏,并且如果您必须处理大量事件处理,事件调用的性能会显着提高。
包含一个单元测试类,该类显示了如何处理 WeakEventDispatcher
。此外,还包括一个用于性能测量的测试方法。它显示了应用标准 .NET 事件处理和应用弱事件调度器的性能差异。
性能测试表明,随着事件处理迭代次数的增加,您会获得更多的性能优势。如果您大幅增加迭代次数,标准 .NET 事件处理最终会因处理程序调用的数量而纠缠在一起,因为“丢失”的实例不会被垃圾回收,因此相应的处理程序会持续被调用。 WeakEventDispatcher
阻止这种情况发生。
以下示例展示了如何应用 WeakEventDispatcher
public class Entity {
private readonly WeakEventDispatcher<EventArgs> _changeNotificationDispatcher;
public event EventHandler<EventArgs> DataChanged {
add { _changeNotificationDispatcher += value; }
remove { _changeNotificationDispatcher -= value; }
}
protected virtual void OnDataChanged(EventArgs e) {
if(_changeNotificationDispatcher!= null)
_changeNotificationDispatcher.Invoke(this, e);
}
}
在事件调度器内部,需要不时进行一些内务处理,即,需要删除属于垃圾回收实例的处理程序。内务处理由 Purge()
执行。清除可以手动执行,但也会定期执行,每隔一个可配置的公共调度器方法调用次数。间隔由 PurgeThreshold
配置,相应的值可以通过调度器的构造函数传递。您可以在声明时实例化调度器并传递一个清除阈值设置,例如
private readonly WeakEventDispatcher<EventArgs>
_changeNotificationDispatcher = new WeakEventDispatcher<EventArgs>(10);
现在您将阈值设置为 10,这意味着每十个 Invoke(...)
或 +=
调用将执行一次清除。因此,使用此值,您可以定义 Purge()
的执行频率。如果传递 0,则每次您触及任何公共调度器方法时都会执行 Purge()
。默认情况下,清除阈值设置为 5。