C # still connected to event after uncoupling

I am currently debugging a large (very large!) C # application that contains memory leaks. It mainly uses Winforms for the GUI, although several controls are created in WPF and hosted with ElementHost. So far, I have discovered that many memory leaks were caused by events that were not detached (by calling - =), and I was able to solve the problem.

However, I ran into a similar problem. There is a class called WorkItem (short-lived), which is registered in the constructor for events of another class ClientEntityCache (long-lived). Events were never untied, and I could see in the .NET profiler that WorkItem instances are kept alive when they shouldn't because of these events. Therefore, I decided to force WorkItem to implement IDisposable, and in the Dispose () function, I canceled the events this way:

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

EDIT

Here is the code I use to subscribe:

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

I also changed the code for unregistering so as not to call the new EntityCacheClearedEventHandler.

END OF EDITING

I made Dispose calls in the appropriate places in the code that the WorkItem uses, and when I debug it, I see that the function is actually being called, and I am = for each event. But I still have a memory leak, and my WorkItems are still alive after Disposed and in the .NET profiler I see that the instances remain alive because event handlers (like EntityCacheClearedEventHandler) still have them in your call list. I tried to unhook them more than once (several - =) to make sure that they were not connected more than once, but this does not help.

Does anyone have an idea why this is happening or what can I do to solve the problem? I believe that I can change event handlers to use weak delegates, but this will require a lot of work with a lot of legacy code.

Thanks!

EDIT:

If this helps, here is the root path described by the .NET profiler: many things point to ClientEntityCache, which points to EntityCacheClearedEventHandler, which points to Object [], which points to another instance of EntityCacheClearedEventHandler (I don’t understand why), which points to WorkItem.

+6
source share
5 answers

There may be several different delegate functions associated with the event. Hopefully the following small example will help to more clearly understand what I mean.

 // 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 } 

If you separate from removing the event handler, you can view the call list in the debugger. See below, there are 2 handlers, one for MyEventHandler1 and another for the MyEventHandler2 method.

enter image description here

Now, after deleting MyEventHandler1 twice, MyEventHandler2 is still registered because there is only one delegate left, it looks a little different, it no longer appears in the list, but until the delegate for MyEventHandler2 is deleted, it will still be referenced by event .

enter image description here

+4
source

When unhooking an event, he must be the same delegate. Like this:

 public class Foo { private MyDelegate Foo = ClientEntityCache_CacheCleared; public void WorkItem() { ClientEntityCache.EntityCacheCleared += Foo; } public void Dispose() { ClientEntityCache.EntityCacheCleared -= Foo; } } 

The reason is that you use syntactic sugar for this:

 public class Foo { public void WorkItem() { ClientEntityCache.EntityCacheCleared += new MyDelegate(ClientEntityCache_CacheCleared); } public void Dispose() { ClientEntityCache.EntityCacheCleared -= new MyDelegate(ClientEntityCache_CacheCleared); } } 

So, -= will not unhook the original you are subscribed to, because these are different delegates.

+2
source

Perhaps try:

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

You create a new event handler and remove it from the delegate - which actually does nothing.

Unsubscribe from events by deleting the link to the original subscription method.

You can always just set your eventhandler = delegate {}; In my opinion, it will be better than null .

0
source

Do you untie the correct link? When you unhook using -= , an error does not occur, and if you disable events that are not connected, nothing will happen. However, if you add the use of += , you will receive an error message if the event is already connected. Now this is just a way to diagnose the problem, but try adding events instead, and if you SHOULD NOT get an error, the problem is that you are detaching the event with the wrong link.

0
source

Dispose will not be called by the GC if the instance is kept alive by event handlers, since it still refers to the event source.

If you called your Dispose method yourself, the links will then go out of scope.

0
source

Source: https://habr.com/ru/post/889127/


All Articles