Why and How to avoid Event Handler memory leaks?

C#Design PatternsMemory LeaksEvent Handling

C# Problem Overview


I just came to realize, by reading some questions and answers on StackOverflow, that adding event handlers using += in C# (or i guess, other .net languages) can cause common memory leaks...

I have used event handlers like this in the past many times, and never realized that they can cause, or have caused, memory leaks in my applications.

How does this work (meaning, why does this actually cause a memory leak) ?
How can I fix this problem ? Is using -= to the same event handler enough ?
Are there common design patterns or best practices for handling situations like this ?
Example : How am I supposed to handle an application that has many different threads, using many different event handlers to raise several events on the UI ?

Are there any good and simple ways to monitor this efficiently in an already built big application?

C# Solutions


Solution 1 - C#

The cause is simple to explain: while an event handler is subscribed, the publisher of the event holds a reference to the subscriber via the event handler delegate (assuming the delegate is an instance method).

If the publisher lives longer than the subscriber, then it will keep the subscriber alive even when there are no other references to the subscriber.

If you unsubscribe from the event with an equal handler, then yes, that will remove the handler and the possible leak. However, in my experience this is rarely actually a problem - because typically I find that the publisher and subscriber have roughly equal lifetimes anyway.

It is a possible cause... but in my experience it's rather over-hyped. Your mileage may vary, of course... you just need to be careful.

Solution 2 - C#

I have explained this confusion in a blog at https://www.spicelogic.com/Blog/net-event-handler-memory-leak-16. I will try to summarize it here so that you can have a clear idea.

Reference means, "Need":

First of all, you need to understand that, if object A holds a reference to object B, then, it will mean, object A needs object B to function, right? So, the garbage collector won't collect object B as long as object A is alive in the memory.

+= Means, injecting reference of Right side object to the left object:

The confusion comes from the C# += operator. This operator does not clearly tell the developer that, the right-hand side of this operator is actually injecting a reference to the left-hand side object.

enter image description here

And by doing so, object A thinks, it needs object B, even though, from your perspective, object A should not care if object B lives or not. As object A thinks object B is needed, object A protects object B from the garbage collector as long as object A is alive. But, if you did not want that protection given to the event subscriber object, then, you can say, a memory leak occurred. To emphasize this statement, let me clarify that, in the .NET world, there is no concept of memory leak like a typical C++ unmanaged program. But, as I said, object A protects object B from garbage collection and if that was not your intention, then you can say a memory leak happened because object B was not supposed to be living in the memory.

enter image description here

You can avoid such a leak by detaching the event handler.

How to make a decision?

There are lots of events and event handlers in your whole code-base. Does it mean, you need to keep detaching event handlers everywhere? The answer is No. If you had to do so, your codebase will be really ugly with verbose.

You can rather follow a simple flow chart to determine if a detaching event handler is necessary or not.

enter image description here

Most of the time, you may find the event subscriber object is as important as the event publisher object and both are supposed to be living at the same time.

Example of a scenario where you do not need to worry

For example, a button click event of a window.

enter image description here

Here, the event publisher is the Button, and the event subscriber is the MainWindow. Applying that flow chart, ask a question, does the Main Window (event subscriber) supposed to be dead before the Button (event publisher)? Obviously No. Right? That won't even make sense. Then, why worry about detaching the click event handler?

An example when an event handler detachment is a MUST.

I will provide one example where the subscriber object is supposed to be dead before the publisher object. Say, your MainWindow publishes an event named "SomethingHappened" and you show a child window from the main window by a button click. The child window subscribes to that event of the main window.

enter image description here

And, the child window subscribes to an event of the Main Window.

enter image description here

From this code, we can clearly understand that there is a button in the Main Window. Clicking that button shows a Child Window. The child window listens to an event from the main window. After doing something, the user closes the child window.

Now, according to the flow chart I provided if you ask a question "Does the child window (event subscriber) supposed to be dead before the event publisher (main window)? The answer should be YES. Right? So, detach the event handler. I usually do that from the Unloaded event of the Window.

A rule of thumb: If your view (i.e. WPF, WinForm, UWP, Xamarin Form, etc.) subscribes to an event of a ViewModel, always remember to detach the event handler. Because a ViewModel usually lives longer than a view. So, if the ViewModel is not destroyed, any view that subscribed event of that ViewModel will stay in memory, which is not good.

Proof of the concept using a memory profiler.

It won't be much fun if we cannot validate the concept with a memory profiler. I have used JetBrain dotMemory profiler in this experiment.

First, I have run the MainWindow, which shows up like this:

enter image description here

Then, I took a memory snapshot. Then I clicked the button 3 times. Three child windows showed up. I have closed all of those child windows and clicked the Force GC button in the dotMemory profiler to ensure that the Garbage Collector is called. Then, I took another memory snapshot and compared it. Behold! our fear was true. The Child Window was not collected by the Garbage collector even after they were closed. Not only that but the leaked object count for the ChildWindow object is also shown as "3" (I clicked the button 3 times to show 3 child windows).

enter image description here

Ok, then, I detached the event handler as shown below.

enter image description here

Then, I have performed the same steps and checked the memory profiler. This time, wow! no more memory leak.

enter image description here

Solution 3 - C#

Yes, -= is enough, However, it could be quite hard to keep track of every event assigned, ever. (for detail, see Jon's post). Concerning design pattern, have a look at the weak event pattern.

Solution 4 - C#

An event is really a linked list of event handlers

When you do += new EventHandler on the event it doesn’t really matter if this particular function has been added as a listener before, it will get added once per +=.

When the event is raised it go through the linked list, item by item and call all the methods (event handlers) added to this list, this is why the event handlers are still called even when the pages are no longer running as long as they are alive (rooted), and they will be alive as long as they are hooked up. So they will get called until the eventhandler is unhooked with a -= new EventHandler.

See Here

and MSDN HERE

Solution 5 - C#

I can tell you that this might possibly become an issue in Blazor. You can have a Component subscribing to events using the += syntax and in the long run, this will cause leaks.

The only solution to this (that I'm aware of) is to not use anonymous methods, have the Component inherit from IDisposable and use Dispose() to unsubscribe the event handler.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiongillybView Question on Stackoverflow
Solution 1 - C#Jon SkeetView Answer on Stackoverflow
Solution 2 - C#Emran HussainView Answer on Stackoverflow
Solution 3 - C#FemarefView Answer on Stackoverflow
Solution 4 - C#TalentTunerView Answer on Stackoverflow
Solution 5 - C#David GuidaView Answer on Stackoverflow