订阅外部事件时事件处理程序被GC自动回收的问题解决方案求助
问题根本原因
最常见的是两种场景导致:
- 场景1:
server.Data属性的Getter每次访问都会返回新的临时对象
你调用server.Data.Received += handler时,事件是绑定在这次Getter返回的临时Data实例上,这个临时对象没有被任何强引用持有,GC运行时就会被回收,绑定的事件自然失效。你现在的临时方案每次重新访问server.Data拿到新的实例重新绑定,所以可以临时生效,完全符合这个场景的特征。 - 场景2:外部库的
Received事件内部用弱引用委托实现
很多封装非托管逻辑的外部库为了避免事件订阅导致的内存泄漏,会用弱引用存储事件委托。这种情况下如果除了事件本身之外,没有其他强引用持有你的委托实例,委托就会被GC回收,事件就无法触发。
排查与解决方案
第一步:先排查是否为server.Data返回临时对象的问题
调用以下代码验证:
bool isSameInstance = Object.ReferenceEquals(server.Data, server.Data);
如果返回false,即可确认是场景1问题,解决方案如下:
在你的类中新增字段持有server.Data的强引用,不要每次访问都调用server.Data的Getter:
private externalLibrary.Server server = new Server(); private externalLibrary.DataHandler handler; // 新增字段持有Data实例的强引用 private externalLibrary.Data _serverData; public bool Subscribe(){ // 仅在订阅时获取一次Data实例,存在字段中 _serverData = server.Data; _serverData.Received += handler; }
修改后事件绑定在你字段持有的固定Data实例上,不会被GC回收,事件就不会丢失。
第二步:如果确认server.Data是同一个实例,按场景2处理
- 首先确认你的当前类实例本身是否存在被GC回收的可能:如果你的当前类实例是局部变量创建、没有被其他长生命周期对象持有的话,整个实例包括
handler字段都会被回收,将当前类实例改为长生命周期的字段持有即可。 - 如果你之前测试的lambda没有单独用字段存储,只是直接写的
server.Data.Received += (s,e) => { },匿名lambda没有强引用持有就会被GC回收,如果要用lambda,需要将lambda赋值给类的字段再绑定。
临时方案的问题
你现在的强制GC+重新绑定的方案有两个明显缺陷:
- 若事件未被GC回收,多次调用
+=会导致同一个处理程序被重复绑定,同一个事件会触发多次处理逻辑,引发业务错误。 - 强制调用
GC.Collect会大幅增加GC压力,影响程序性能,不适合长期使用。
内容的提问来源于stack exchange,提问作者Guillaume Globensky
相关产品推荐
相关产品推荐

