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.
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.
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:
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.
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.
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.
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:
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
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.