我目前正在调试一个包含内存泄漏的大型(非常大!)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
}
修改
以下是我用于订阅的代码:
public WorkItem()
{
ClientEntityCache.EntityCacheCleared += ClientEntityCache_CacheCleared;
// Same thing for 10 other events
}
我还将取消注册的代码更改为不调用新的EntityCacheClearedEventHandler。
编辑结束
我在使用WorkItem的代码中的适当位置调用了Dispose,当我调试时,我可以看到函数真正被调用,而且我做 - =对于每个事件。但是我仍然得到内存泄漏,我的WorkItems在Disposed之后仍然存活,在.NET Profiler中我可以看到实例保持活动,因为事件处理程序(如EntityCacheClearedEventHandler)仍然在它们的调用列表中有它们。我试图解开它们不止一次(倍数= =)只是为了确保它们不会被连接多次,但这没有帮助。
任何人都知道为什么会这样或者我能做些什么来解决问题? 我想我可以更改事件处理程序以使用弱委托,但这需要用大量遗留代码来搞乱。
谢谢!
编辑:
如果这有帮助,这是.NET Profiler描述的根路径: 很多事情都指向ClientEntityCache,它指向EntityCacheClearedEventHandler,它指向Object [],它指向EntityCacheClearedEventHandler的另一个实例(我不明白为什么),它指向WorkItem。
答案 0 :(得分: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
}
如果在删除事件处理程序之前中断,则可以在调试器中查看调用列表。见下文,有2个处理程序,一个用于MyEventHandler1,另一个用于MyEventHandler2方法。
现在,在删除MyEventHandler1两次之后,MyEventHandler2仍然被注册,因为只剩下一个委托它看起来有点不同,它不再显示在列表中,但是直到MyEventHandler2的委托被删除它仍然会被引用事件。
答案 1 :(得分:2)
取消事件时,它需要是同一个委托。像这样:
public class Foo
{
private MyDelegate Foo = ClientEntityCache_CacheCleared;
public void WorkItem()
{
ClientEntityCache.EntityCacheCleared += Foo;
}
public void Dispose()
{
ClientEntityCache.EntityCacheCleared -= Foo;
}
}
原因是,你使用的是语法糖:
public class Foo
{
public void WorkItem()
{
ClientEntityCache.EntityCacheCleared +=
new MyDelegate(ClientEntityCache_CacheCleared);
}
public void Dispose()
{
ClientEntityCache.EntityCacheCleared -=
new MyDelegate(ClientEntityCache_CacheCleared);
}
}
所以-=
并没有取消你订阅的原始版本,因为它们是不同的代表。
答案 2 :(得分:0)
也许试试:
public void Dispose()
{
ClientEntityCache.EntityCacheCleared -= ClientEntityCache_CacheCleared;
// Same thing for 10 other events
}
您正在创建一个新的事件处理程序并将其从delegate
中删除 - 这实际上无效。
删除对 原始 订阅事件方法的引用,删除活动订阅。
您可以随时设置eventhandler = delegate {};
在我看来,这会比null
更好。
答案 3 :(得分:0)
你是否正在解开正确的参考?使用-=
取消挂钩时,不会产生任何错误,如果您正在解除未挂钩的事件,则不会发生任何错误。但是,如果使用+=
添加,如果事件已被挂钩,则会出现错误。现在,这只是一种诊断问题的方法,但是尝试添加事件,如果你 DONT 得到错误,问题是你用错误的引用解开事件。
答案 4 :(得分:0)
如果事件处理程序使实例保持活动状态,GC将不会调用它,因为它仍然被事件源引用。
如果您自己调用了Dispose方法,则引用将超出范围。