Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

Event Handler Memory Leaks, Unwiring Events, and the WeakEventManager in WPF 4.5

Pete Brown - 01 February 2012

Many developers don't realize that a common source of memory leaks in .NET applications is the event handler. WPF 4.5 includes built-in support for weak events to help us do the right thing and not eat up all the available memory.

The Problem

Events are ubiquitous in .NET. When you add an event handler using the EventName += <my event handler delegate> syntax, the event source acquires a reference to the listener. This situation is a circular reference, as the listener had a reference to the source to begin with.

These references are "strong references". That is, they are actual pointers to resources.

image

In the .NET framework, circular references aren't usually a problem. If the set of objects can't be traced back to the root object in the application, they are garbage collected. That's great, but in this case, due to the references from the event source to the event listener, as long as the event source is reachable, the listener will also stay around, as the event source can be traced back to a valid object root.

This can be a real problem if the event source is long-lived, as is the case with things like application configuration objects, or other static or singleton objects. Event handlers are often on the UI side of things, so the listener can be a resource and memory heavy Window object.

Take this scenario: Over the course of an application's lifetime, the user opens and closes document windows. The document windows each have a reference to some other object: it could be the parent window, it could be a static configuration class, it could be a shared singleton data provider somewhere up the chain. The important thing is that the windows are listening to an event from that object: CollectionChanged, some custom DataLoaded or other event.

image

Now, when the parent window (and your code) releases all reference to the window instance, the event source still has a reference to them. And, because the event source also has a reference from the application (implicit if it's static - man that lifetime gets ugly), the windows stay around:

image

No GC or disposal for those window instances. Windows tend to have a lot of resources tied up just to paint (control templates, controles themselves, data even) so they're not cheap resources. It gets even nastier, though. If those instances have references to, say, individual ViewModel instances, and those ViewModels have references to a cache of data, you can end up with pretty enormous memory leaks. Open up enough windows and, eventually, your app will run out of resources.

Basis for Comparison

Before we look at the problem, here's a profile of the application opening 200 windows, closing the 200 windows, and then forcing a GC. I did this three and then closed the app. Each window allocates a bunch of memory in an array just to give it a payload. The window code looks like this:

public partial class TestChildWindow : Window
{
long[] _memoryHog;

public TestChildWindow()
{
InitializeComponent();

Random rnd = new Random();

string imageName = "Assets/image" + rnd.Next(1, 9) + ".jpg";
NeatoImage.Source = new BitmapImage(new Uri(imageName, UriKind.Relative));

_memoryHog = new long[1024];

Unloaded += new RoutedEventHandler(TestChildWindow_Unloaded);
}

void TestChildWindow_Unloaded(object sender, RoutedEventArgs e)
{
}
}

 

In the upcoming examples, the TestChildWindow will have a reference to the SettingsManager. This is a stub class meant to represent pretty much any long-lived instance in your application.

namespace WpfWeakEventManager
{
class SettingsManager
{
public event EventHandler SettingsChanged;

private static SettingsManager _current;
public static SettingsManager Current
{
get
{
if (_current == null)
_current = new SettingsManager();

return _current;
}
}
}
}

Finally, the Main Window has three buttons that I used to test the application. The first opens 200 windows. The second closes all windows (except the main one) being tracked by the application. The third forces a GC collection. It also has a listbox to show diagnostic information when I'm not running with an attached debugger.

namespace WpfWeakEventManager
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
private SettingsManager _current;

public MainWindow()
{
InitializeComponent();

// make sure SettingsManager is referenced outside
// just the child windows. Normally this would happen
// just do to general use of the class. Forcing it here.

_current = SettingsManager.Current;
}

private void OpenWindowsButton_Click(object sender, RoutedEventArgs e)
{
int openedCount = 0;
DebugList.Items.Add("Total Memory Before Opening Windows: " + GC.GetTotalMemory(true));

for (int i = 0; i < 200; i++)
{
var window = new TestChildWindow();
window.Show();
openedCount++;
}

DebugList.Items.Add("Opened " + openedCount + " windows.");
DebugList.Items.Add("Total Memory After Opening Windows: " + GC.GetTotalMemory(true));
}

private void CloseWindowsButton_Click(object sender, RoutedEventArgs e)
{
int closedCount = 0;
foreach (Window window in Application.Current.Windows)
{
if (window != this)
{
window.Close();
closedCount++;
}
}

DebugList.Items.Add("Closed " + closedCount + " windows.");
DebugList.Items.Add("App has " + Application.Current.Windows.Count + " window(s) it's holding on to.");
}

private void CollectButton_Click(object sender, RoutedEventArgs e)
{
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
//GC.Collect();
}
}
}

I experimented with a few variations on GC.Collect and some of the other notifications before the code that is here. A plain old GC.Collection will likely work just as well in any case.

Here's what the window looks like after I opened 200 windows and then closed them and then opened another 200. 200 windows is actually a lot of windows, as it turns out, but if you consider an application where your user opens up 1 customer record every 5 minutes (for a phone support person, this is conservative), over the course of an 8 hour day, that could be as many as 160 forms, and that's assuming they close the application every day when they leave.

image

Now I needed to find a good way to track memory usage. The profiler in Visual Studio is really good for finding performance bottlenecks, but not really meant for the real-time memory usage graphing I wanted to do. So, I downloaded the 10 day trial of .NET Memory Profiler (an app I used a lot back when I was consulting) and had it capture data while I performed the above actions.

image

In this graph, the pink line is the active memory in use in bytes, the green line is the number of root references. (note: none of the memory graphs include memory from on-window images. I ran without the cute images, but I left the code in the form and the images in the screenshot just because)

Remember, this is without any of the circular reference problems discussed so far. Now, let's take a look at the same actions when I have an event handler wired up in the constructor of the window.

image

Notice something about that graph? Yep. Memory just isn't being returned. It's leaking, and the root references keep on climbing. The only difference between this profiled version and the previous was that I added the SettingsManager event handler wireup in the constructor:

public partial class TestChildWindow : Window
{
long[] _memoryHog;

public TestChildWindow()
{
InitializeComponent();

Random rnd = new Random();

string imageName = "Assets/image" + rnd.Next(1, 9) + ".jpg";
NeatoImage.Source = new BitmapImage(new Uri(imageName, UriKind.Relative));

_memoryHog = new long[1024];

SettingsManager.Current.SettingsChanged += Current_SettingsChanged;

Unloaded += new RoutedEventHandler(TestChildWindow_Unloaded);
}

void Current_SettingsChanged(object sender, EventArgs e)
{
// do something
}

void TestChildWindow_Unloaded(object sender, RoutedEventArgs e)
{
}
}

The Unloaded handler is empty, but it is wired up. The key change here is the SettingsManager.Current.SettingsChanged wire-up.

There are a multitude of considerations in this example, but let's deal with just the topic of this post: the event handlers.

Solution 1: Unwire Your Event Handlers

This generally doesn't work if you use anonymous delegates (one reason to be careful about using them with objects that live outside the scope of a single method), but in cases where you have a real event handler method, unhooking them when you're done with them is definitely a best practice.

You can remove event handlers in Dispose methods, or in the case of a window, in the Unload event handler. That's what I did here.  The code is a simple change:

void TestChildWindow_Unloaded(object sender, RoutedEventArgs e)
{
SettingsManager.Current.SettingsChanged -= Current_SettingsChanged;
}

All I did was remove the event handler inside the Unloaded handler. Take a look at the impact it had on memory:

image

That's much better. The root references and memory both drop when I hit the "collect" button. Compare this to the previous example where the memory never went down. As to the differences between this graph and the first one, chalk it up to sample timing and my own non-deterministic button pushing.

So, that's a great approach. In fact, if you can do this, you should do this. It's simple and highly effective.

For other situations, perhaps where you don't control all the code, or when unwiring events is difficult or inconvenient, there's the Weak Event Pattern.

Solution 2: Weak Events

It's not always possible or practical to unwire event handlers. Actually, let me rephrase that: sometimes the level of effort and thought that needs to go into *where* you put event handler unwiring is more than developers have time to put into the app. :)

Enter the Weak Event Pattern

The concept of a Weak Event is nothing new. Many others have created their own various weak event solutions for use with WPF. Previous versions of .NET and WPF included the WeakEventManager, but required you to derive your own class and implement IWeakEventListener interfaces. That was a fair bit of work for developers who just need to get stuff done. The real news here is that WPF 4.5 includes the generic WeakEventManager type to make it easier for you to implement the weak event pattern in your own code, without all the other ceremony. You can still derive from WeakEventManager (if you can do the work, it's actually a good practice due to the performance benefits you'll get from it via having actual events and not reflection look-ups), but the new generic class makes it easy enough so that there's no excuse *not* to do it.

Here's the memory usage of the application using Weak Events

image

And here's the code change. I removed the code to unwire the event, and did the event wireup using WeakEventManager rather than regular old events.

namespace WpfWeakEventManager
{
/// <summary>
/// Interaction logic for TestChildWindow.xaml
/// </summary>
public partial class TestChildWindow : Window
{
long[] _memoryHog;

public TestChildWindow()
{
InitializeComponent();

Random rnd = new Random();

string imageName = "Assets/image" + rnd.Next(1, 9) + ".jpg";
NeatoImage.Source = new BitmapImage(new Uri(imageName, UriKind.Relative));

_memoryHog = new long[1024];

// this version wires up an event handler which causes a hard reference
// and, unless it is unwired, a memory leak
// SettingsManager.Current.SettingsChanged += Current_SettingsChanged;

// unwire the event handler. This is a good practice for avoiding leaks
// Unloaded += new RoutedEventHandler(TestChildWindow_Unloaded);

// this version avoids memory leaks without requiring you to
// unwire the event.
WeakEventManager<SettingsManager, EventArgs>
.AddHandler(SettingsManager.Current,
"SettingsChanged",
Current_SettingsChanged);
}

void Current_SettingsChanged(object sender, EventArgs e)
{
// do something
}

void TestChildWindow_Unloaded(object sender, RoutedEventArgs e)
{
// SettingsManager.Current.SettingsChanged -= Current_SettingsChanged;
}
}
}

This new WeakEventManager is much easier to use than anything we had before. You take a hit for relying on reflection to resolve the event name, but in all but the most performance-sensitive cases (where you probably wouldn't bother with events anyway), it's going to be worth it.

Summary

Event handlers and long-lived objects can be the source of memory leaks in any .NET application. Whenever you can, you should unwire your event handlers to help avoid these leaks. When that is impractical, the Weak Event Pattern using the WPF 4.5 WeakEventManager<TObject, TArgs> is a great way to help save you from the grief of memory leaks without requiring all the ceremony we had with the regular WeakEventManager and IWeakEventListener in the previous versions.

Now there's no excuse to have leaky event handlers.

     

Source Code and Related Media

Download /media/83303/wpfweakeventmanager.zip
posted by Pete Brown on Wednesday, February 1, 2012
filed under:      

9 comments for “Event Handler Memory Leaks, Unwiring Events, and the WeakEventManager in WPF 4.5”

  1. Laurent Bugnionsays:
    Hey Pete,

    It's a nice feature. That said, WeakEvents in WPF are less the issue than in Silverlight (and WP7) and Windows 8, where reflection doesn't allow you the same degree of freedom as in .NET. Do you have any hints about implementing a WeakEvent pattern there?

    For SL/WP7, I have a good solution for public named event handlers, but it doesn't work for private or anonymous ones (because of security limitations). For Windows 8, it is worse, because the API changed, and right now i do not have a good solution.

    Any hint?
    Cheers,
    Laurent
  2. Shimmysays:
    Hi Pete and thanks for the post!

    Pitty event name must be specified with a string.
    This feature will be possible if special type classes will be approved for generic type constraints (i hope i'm not wrong, just shooting out from my mind, didn't dig into it too deeply).

    An expression tree cannot allow delegates and enums and other.
    It's a pretty compelling feature tho, I wish I could be able to say where T : enum / where T : delegate etc.

    This might be the door for specifying the target by passing in an Expression<Func<TSource, EventHandler<TEventArgs>>> that points to an event as an argument.

    I really hope this feature goes in to Silverlight any time soon.
    Silverlight doesn't allow private method invoking which makes the whole issue much harder.

    Read this: http://blogs.microsoft.co.il/blogs/shimmy/archive/2012/02/01/weak-event-handler-for-silverlight.aspx
  3. Shimmysays:
    Laurent, I found a simple solutions for invoking private methods on the target object, have them implement an interface. All my attempts using reflection failed, SL doesn't allow private method invoking in untrusted apps (never tried trusted tho)
    Read this: http://blogs.microsoft.co.il/blogs/shimmy/archive/2012/02/01/weak-event-handler-for-silverlight.aspx
    Any comments and improvements are really welcomed!
  4. Geert van Horriksays:
    I agree with Laurent. I have written a weak event handler that correctly implements weak events, but then you are forced to use public methods.

    For the solution I used, see http://blog.catenalogic.com/post/2011/11/23/A-weak-event-listener-for-WPF-Silverlight-and-Windows-Phone-7.aspx. However, it would by very, very nice if we could also use private methods or anonymous delegates (which are public methods on a private class).

    Implementing an interface on target classes is not an option, simply because it takes away the easy of using weak events at all.

    Thanks for the effort.
  5. Samsays:
    The idea is nice, but in practice I would never use this feature because of the magic string requirement. I never understood why so many new features always default to using magic strings and then only later on develops a type safe solution.

    There really needs to be support added into C# so the compiler can resolve property/event names without requiring runtime reflection or the use of magic strings.

    In addition, I think an even simpler solution would be to offer some kind of support (in a library or at the language-level) for iterating over the delegates attached to an event. That way you can remove events even if you were not the one to have attached them in the first place. It seems bizarre that it's possible to change all other public references of an object, but not the delegates to its events - unless you already have a reference to that delegate.
  6. memo-bagwisays:
    namespace WpfWeakEventManager
    {
    /// <summary>
    /// Interaction logic for TestChildWindow.xaml
    /// </summary>
    public partial class TestChildWindow : Window
    {
    long[] _memoryHog;

    public TestChildWindow()
    {
    InitializeComponent();

    Random rnd = new Random();

    string imageName = "Assets/image" + rnd.Next(1, 9) + ".jpg";
    NeatoImage.Source = new BitmapImage(new Uri(imageName, UriKind.Relative));

    _memoryHog = new long[1024];

    // this version wires up an event handler which causes a hard reference
    // and, unless it is unwired, a memory leak
    // SettingsManager.Current.SettingsChanged += Current_SettingsChanged;

    // unwire the event handler. This is a good practice for avoiding leaks
    // Unloaded += new RoutedEventHandler(TestChildWindow_Unloaded);

    // this version avoids memory leaks without requiring you to
    // unwire the event.
    WeakEventManager<SettingsManager, EventArgs>
    .AddHandler(SettingsManager.Current,
    "SettingsChanged",
    Current_SettingsChanged);
    }

    void Current_SettingsChanged(object sender, EventArgs e)
    {
    // do something
    }

    void TestChildWindow_Unloaded(object sender, RoutedEventArgs e)
    {
    // SettingsManager.Current.SettingsChanged -= Current_SettingsChanged;
    }
    }
    }
    public partial class TestChildWindow : Window
    {
    long[] _memoryHog;

    public TestChildWindow()
    {
    InitializeComponent();

    Random rnd = new Random();

    string imageName = "Assets/image" + rnd.Next(1, 9) + ".jpg";
    NeatoImage.Source = new BitmapImage(new Uri(imageName, UriKind.Relative));

    _memoryHog = new long[1024];

    SettingsManager.Current.SettingsChanged += Current_SettingsChanged;

    Unloaded += new RoutedEventHandler(TestChildWindow_Unloaded);
    }

    void Current_SettingsChanged(object sender, EventArgs e)
    {
    // do something
    }

    void TestChildWindow_Unloaded(object sender, RoutedEventArgs e)
    {
    }
    }


Comment on this Post

Remember me