脱钩后,C#仍然挂钩了一个事件

Car*_*arl 7 c# events hook memory-leaks

我目前正在调试一个包含内存泄漏的大型(非常大的!)C#应用程序.它主要使用Winforms作为GUI,尽管在WPF中制作了几个控件并使用ElementHost进行托管.到目前为止,我发现许多内存泄漏是由事件没有解开(通过调用 - =)引起的,我已经能够解决问题了.

但是,我刚刚遇到了类似的问题.有一个名为WorkItem(短期)的类,它在构造函数中注册到另一个名为ClientEntityCache(long long)的类的事件.这些事件从来没有被解开,我可以在.NET Profiler中看到WorkItem的实例在不应该因为那些事件而保持活着状态.所以我决定让WorkItem实现IDisposable,并在Dispose()函数中以这种方式取消事件:

public void Dispose()
{
  ClientEntityCache.EntityCacheCleared -= ClientEntityCache_CacheCleared;
  // Same thing for 10 other events
}
Run Code Online (Sandbox Code Playgroud)

编辑

这是我用于订阅的代码:

public WorkItem()
{
  ClientEntityCache.EntityCacheCleared += ClientEntityCache_CacheCleared;
  // Same thing for 10 other events
}
Run Code Online (Sandbox Code Playgroud)

我还将取消注册的代码更改为不调用新的EntityCacheClearedEventHandler.

编辑结束

我在使用WorkItem的代码中的适当位置调用了Dispose,当我调试时,我可以看到该函数真的被调用,而且我做 - =每个事件.但我仍然得到内存泄漏,我的WorkItems在Disposed之后仍然存活,在.NET Profiler中我可以看到实例保持活动,因为事件处理程序(如EntityCacheClearedEventHandler)仍然在它们的调用列表中有它们.我试图解开它们不止一次(倍数= =)只是为了确保它们不会被连接多次,但这没有帮助.

任何人都知道为什么会这样或者我能做些什么来解决问题?我想我可以更改事件处理程序以使用弱委托,但这需要大量的遗留代码.

谢谢!

编辑:

如果这有帮助,这里是.NET分析器描述的根路径:ClientEntityCache上有很多东西指向EntityCacheClearedEventHandler,它指向Object [],它指向另一个EntityCacheClearedEventHandler实例(我不明白为什么),它指向WorkItem.

Chr*_*lor 4

可能有多个不同的委托函数连接到该事件。希望下面的小例子能让我更清楚地理解我的意思。

// Simple class to host the Event
class Test
{
  public event EventHandler MyEvent;
}

// Two different methods which will be wired to the Event
static void MyEventHandler1(object sender, EventArgs e)
{
  throw new NotImplementedException();
}

static void MyEventHandler2(object sender, EventArgs e)
{
  throw new NotImplementedException();
}


[STAThread]
static void Main(string[] args)
{
  Test t = new Test();
  t.MyEvent += new EventHandler(MyEventHandler1);
  t.MyEvent += new EventHandler(MyEventHandler2); 

  // Break here before removing the event handler and inspect t.MyEvent

  t.MyEvent -= new EventHandler(MyEventHandler1);      
  t.MyEvent -= new EventHandler(MyEventHandler1);  // Note this is again MyEventHandler1    
}
Run Code Online (Sandbox Code Playgroud)

如果在删除事件处理程序之前中断,则可以在调试器中查看调用列表。请参阅下面,有 2 个处理程序,一个用于 MyEventHandler1,另一个用于方法 MyEventHandler2。

在此输入图像描述

现在,在两次删除 MyEventHandler1 后,MyEventHandler2 仍然被注册,因为只剩下一个委托,它看起来有点不同,它不再显示在列表中,但在 MyEventHandler2 的委托被删除之前,它仍然会被事件引用。

在此输入图像描述