Unlocking the Secrets of Managed Memory: Dive into Event Handler Leak Insights!

Massimo Giusti

Event handler leaks have been around for a long time, and they are one of the peskiest issues WPF (Windows Presentation Foundation) developers regularly deal with. You might be thinking: What makes event handler leaks so important? Event handler leaks are easy to cause, all it takes is to forget to unsubscribe to an event. Additionally, they are quite difficult to spot and even trickier to fix.

The new insight added to the Visual Studio Managed Memory Usage Tool introduced in update 17.9 Preview 1 significantly trivializes the process of spotting/fixing these leaks. It provides information on which objects are leaking and the event it subscribed to.

What is an Event Handler Leak?

An object leaks on the heap when it is functionally out of use, but not identified for Garbage Collection. This means that it will unintentionally remain alive in memory. Event handlers are notorious for causing this scenario. This is because event handlers create a direct reference between an object and the event it has subscribed to.

Figure 1: UML of pub/sub model with events in C#

In this example, we have a Publisher and Subscriber class. When Subscriber calls Subscribe(publisher), MyEvent will link Publisher and Subscriber in the heap:

Image Screenshot 2023 09 05 112650

Figure 2: Heap view of Figure 1

The issue with this is that if Subscriber forgets to unsubscribe, these references remain on the heap, thus leaking Subscriber. The trivial solution here is to simply call the unsubscribe method, but in more complex applications, it is difficult to track an object’s subscriptions and when to unsubscribe. This is where the Memory Analyzer can come in and help developers fix these issues.

Okay, How Do We Fix it?

To demonstrate the insight, we will do a walkthrough debugging of a sample WPF app to find an event handler leak:

Figure 3: Sample code of a WPF application

In this case, we have a window which subscribes to the dispatcherTimer_Tick event when opened. What the event is doing doesn’t really matter. The important part of this code is where we forget to unsubscribe to the event when the window gets closed:

Figure 4: Code that causes event handler leak in the sample WPF app

The commenting-out of the unsubscribe statement here is problematic since it no longer unsubscribes properly as the window closes and will cause a leak. To find it, let’s start debugging this app (F5). For simplicity, let’s assume main() properly causes the leak, it opens AdWindow, causing it to subscribe to an event, then closes it.

First, we need the Diagnostic Tools window open. To get to it in a debugging session, go to Debug -> Windows -> Diagnostic Tools.

Figure 5: How to navigate to the Memory Analysis Tool

It should look like this when it is open in a debug session:

Figure 6: Memory Analysis Tool view in Debugger

This window shows the overall size of the heap and the CPU% for the application being debugged. When we click the camera icon to take a snapshot, we can view the heap and access the event handler leak insight by clicking the value under Objects.

Figure 7: How to take and view a snapshot

Once we are in the heap view of the snapshot, navigate to Insights:

Figure 8: How to navigate to the Insights tab

We are finally here! In the Insights Tab, we can see the list of leaking event handlers, and we can see that our leaking window shows up. Additionally, we can see the total amount wasted by that leak. Just that one simple example caused a leak of 4.93 KB! This is because the window has a whole subtree of objects it references that also are leaking just from forgetting to unsubscribe.

Figure 9: Instance view of event handler leaks

Additionally, you can filter out all the system code by clicking ’Show Just My Code’ to show just AdWindow.

That’s Cool, But What Now?

So far, we have successfully identified the leak in our app. Now if we want to fix it, we can click ‘View Details’ to see more information on what the issue is and more importantly, how to fix it.

Figure 10: Details view of event handler leak

This view shows us some critical information about the leak. We can see the address of the object, the event handler that it is holding onto, and most importantly, the object it subscribed to. This conveys that to fix the issue, AdWindow must unsubscribe from DispatcherTimer. Additionally, you can see the reference graph of the AdWindow object. The ‘Referenced Objects’ tab shows just how many additional objects are leaked due to AdWindow.

What Else Can We Do?

If you’ve made it this far, you might be interested in the other uses this insight has. For instance, the detection logic can handle any kind of Event handler. Consider the console app with a publisher and subscriber class from earlier, if we create our own event handler, we can still detect it.

Figure 11: Details view of event handler leak with custom event handler type

Tell us what you think!

There are still plenty of improvements to come in future versions of this experience. Please Download the latest Visual Studio Preview and provide your feedback. Please raise issues and provide feedback within the Visual Studio with “Report a Problem” or directly at the Developer Community site.

 

13 comments

Comments are closed. Login to edit/delete your existing comments

  • David Lowndes 4

    >Now if we want to fix it, we can click ‘View Details’
    Where’s the UI to invoke that? I can’t see anything in Fig 9.

    • Rich K 0

      While the debugger is still running, there’s a small icon (looks like a circle overlapping a grid) right aligned at the end of the ‘Object Type’ column for each row; just before the ‘Wasted’ column starts. Hover over that icon and you see the tooltip ‘Show Details’. Click the icon to show the detail view for that line.

      If you stop debugging (shift-F5) while showing the Managed Memory/Insights view, the details are no longer available, and the little icons will disappear; apparently thats what fig 9 captured.

  • Daniel Smith 3

    This looks really handy. I assume this applies equally to WinForms as well?

    Is it just events on IDisposable classes that we need to worry about unsubscribing? I take it we don’t need unsubscribe from button click events etc.?

    • Michael Taylor 2

      Actually `IDisposable` has nothing to do with this. The case you don’t think you need to worry about is the exact case that you have to worry about and, hence, why this tool is useful. To subscribe to an event you need a delegate. A delegate is the instance to be called and the method on the instance. The default event implementation that most events use require a strong reference to the instance.

      Now imagine that you have a main window that displays child windows. Your main window wants to notify child windows of some important event so you expose the event and child windows subscribe to it. Now also imagine that you expose an option to allow the user to show and hide the window. When the user shows the window you create an instance of your child window, subscribe to the main window’s event and show the window. When the user closes the child window you get rid of the instance. But because the main window has a delegate that references that child window the instance in memory cannot be garbage collected by the runtime. In fact every time the event is raised that instance gets the event notification (even though the window isn’t even visible anymore). This is made much worse if the user keeps opening and closing the child window because your code keeps creating new instances, adding them to the event list and then closing the window. If the program itself runs all day (or even days) then you can end up eating up a lot of resources. Until the main window is cleaned up then all the child windows are still referenced. This is made even worse given that the longer an instance is alive the less likely it is to get cleaned up. Hence a child window the user showed early in the program’s lifetime can end up lasting the entire lifetime of the process.

      This is why you should always disconnect from any external events (e.g. the main window) when your window closes. You don’t need to disconnect from events that your child controls are raising because when the parent window is cleaned up so are the child controls and thus the events are going to get disconnected. But any external events should be explicitly cleaned up. That is what this tool is detecting.

  • Rolf van Kuijen 0

    Didn’t get any “Event Handlers Leaks” category with 17.8.3 nor with 17.9.0 Preview 2, anything specific needed for that.

    All aside the insights will help for common pitfalls with UI coding, especially those old ones made, and with iOS where that is most likely to happen due the heavy requirement to use weak references as that platform plays really weird with references being retained even though you would expect it to be gone.

    However I think a small footprint in the blog might be needed that in your example the memory leak occurs because the DispatcherTimer is using Timer that has a singleton design in it that will not release reference unless disposed, in case of DispatcherTimer using Stop.
    This to prevent people might start adding left and right unsubscribing of events which do play nice and get removed normally.

    • Brian Runck 0

      I imagine this will only be available for applications using the CLR. .NET for iOS uses the Mono runtime. It would be great if they can get this tooling to work on both runtimes, but I see no signs of that (yet).

      • Alexander Wurzinger 3

        I tried with a Dump of a .Net FW 4.8, and also a Snpashot of the running process, but I only have ‘Duplicate Strings’ and ‘Sparse Arrays’ in the Register ‘Insights’. This is with VS Version 17.9.0 Preview 2.
        Is this insight maybe only aviable with the .Net Core Framework?
        Or is there some other contition?

        • Brett Haney 2

          I was wondering the same thing. VS Version 17.9.0 Preview 2.0 here. I’m debugging an ASP.NET Framework 4.8 application and in the “Insights” tab all I have are “Duplicate Strings” and “Sparse Arrays”.

  • J. Portelli 0

    Is there a mistake in the article? Are you referring to 17.9 Preview 2 which is available right now?

    • Harshada HoleMicrosoft employee 1

      Apologies for the confusion this feature is available in 17.9 Preview 1.

      • J. Portelli 0

        Thank you 🙏

  • John B 0

    How does it know I’m done with the subscriber, in general?

  • Harshada HoleMicrosoft employee 1

    Apologies for the inconvenience. The feature is in 17.9 Preview 1 and is compatible with both .NET Framework and .NET 5+.

Feedback usabilla icon