Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/codeigniter/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
脱钩后,C#仍对某项活动感兴趣_C#_Events_Memory Leaks_Hook - Fatal编程技术网

脱钩后,C#仍对某项活动感兴趣

脱钩后,C#仍对某项活动感兴趣,c#,events,memory-leaks,hook,C#,Events,Memory Leaks,Hook,我目前正在调试一个包含内存泄漏的大型(非常大!)C#应用程序。它主要使用Winforms作为GUI,尽管有几个控件是在WPF中创建的,并由ElementHost托管。到目前为止,我发现很多内存泄漏都是由未解除挂钩的事件(通过调用-=)引起的,我已经解决了这个问题 然而,我刚刚遇到了一个类似的问题。有一个名为WorkItem(短期)的类,它在构造函数中注册到另一个名为ClientEntityCache(长期)的类的事件。这些事件从未被取消挂钩,我可以在.NET事件探查器中看到WorkItem的实例

我目前正在调试一个包含内存泄漏的大型(非常大!)C#应用程序。它主要使用Winforms作为GUI,尽管有几个控件是在WPF中创建的,并由ElementHost托管。到目前为止,我发现很多内存泄漏都是由未解除挂钩的事件(通过调用-=)引起的,我已经解决了这个问题

然而,我刚刚遇到了一个类似的问题。有一个名为WorkItem(短期)的类,它在构造函数中注册到另一个名为ClientEntityCache(长期)的类的事件。这些事件从未被取消挂钩,我可以在.NET事件探查器中看到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调用,当我调试时,我可以看到函数确实被调用了,并且我对每个事件都执行-=操作。但我仍然会出现内存泄漏,我的工作项在被释放后仍然保持活动状态,在.NET profiler中,我可以看到实例保持活动状态,因为事件处理程序(如EntityCacheClearedEventHandler)的调用列表中仍然有它们。我多次尝试将它们解开(多次-=),只是为了确保它们不会被钩住多次,但这并没有帮助

有人知道为什么会发生这种情况,或者我能做些什么来解决这个问题吗? 我想我可以将事件处理程序更改为使用弱委托,但这需要大量的遗留代码

谢谢

编辑:

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

也许可以尝试:

 public void Dispose()
    {
      ClientEntityCache.EntityCacheCleared -= ClientEntityCache_CacheCleared;
      // Same thing for 10 other events
    }
您正在创建一个新的事件处理程序,并将其从
委托中删除,这实际上不起任何作用

通过删除对原始订阅事件方法的引用来删除事件订阅


您可以随时设置
eventhandler=delegate{}
在我看来,这比
null

要好。您是否解开了正确的引用?当您使用
-=
解除钩住时,不会产生任何错误,如果您正在解除钩住未钩住的事件,则不会发生任何事情。但是,如果使用
+=
添加,如果事件已挂接,则会出现错误。现在,这只是您诊断问题的一种方法,但是请尝试添加事件,如果您没有得到错误,那么问题在于您使用错误的引用解除事件的挂钩

如果事件处理程序保持实例的活动状态,则GC不会调用Dispose,因为它仍然被事件源引用


如果您自己调用Dispose方法,则引用将超出范围。

取消事件挂钩时,它必须是同一个委托。像这样:

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);
     }
}

因此,
-=
不会解开您订阅的原始代理,因为它们是不同的代理。

可能是多个不同的代理函数连接到事件。希望下面的小例子能让我的意思更清楚

// 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    
}
如果在删除事件处理程序之前中断,则可以在调试器中查看调用列表。如下所示,有两个处理程序,一个用于MyEventHandler1,另一个用于MyEventHandler2方法

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


能否向我们显示事件订阅代码?是否从不同线程调用Dispose?您是否正在为事件使用自定义添加/删除?委托状态不能损坏(因为它是不可变的),但如果实现自定义添加/删除且不包括默认实现锁定,则多个添加或删除操作可能会相互干扰。您可能希望在调试器中停止并查看调用列表(Delegate.GetInvocationList)并查看列出了多少个事件处理程序。可能有一些连接的事件处理程序未绑定到ClientEntityCache\u CacheCleared,而是其他一些函数。您确定没有其他任何东西引用这些工作项吗?我不确定实现IDisposable是否是最好的方法,您试图处理的资源不是非托管的,应该在下一个周期由GC释放。您是否尝试过将委托设置为null而不是删除事件处理程序?是否有多个处理程序与该事件关联?@Marcus King:是的,我确信没有任何其他处理程序引用工作项。我可以在.NET profiler中看到,唯一引用它们的是事件处理程序。对于IDisposable,我同意这可能不是最好的解决方案,但我没有方法调用来告诉WorkItem取消注册,所以我尝试了。也许我应该创建一个名为CleanUp或类似的方法。实际上,编译器会隐式添加一个新的EntityCacheClearedEventHandler(ClientEntityCache\u CacheCleared)
。我只是尝试通过执行ClientEntityCache.EntityCacheCleared+=ClientEntityCache\u CacheCleared;并通过执行ClientEntityCache.EntityCacheCleared-=ClientEntityCache\u CacheCleared来取消注册;而不是叫新的