In order to avoid creating memory leaks in WPF applications you need to know how to cause them. Here’s a list of the most common situations that cause memory leaks.
- Event handlers to objects in parent windows
- Registering to events from static objects
- Using timers
- Data binding
- Changing the Text property of a text box
The Sample Application
I made a very simple application that demonstrates different cases of memory leaks.
It has several buttons each launching a specific memory leak case. On the top there’s a “Collect Garbage” button that forces a garbage collection, so that you can see if the memory gets cleaned or not. Each case has a checkbox that represents the solution / fix to the memory leak.
To help us see the memory leaks, I added a 100 MB array in each of the child windows, so it’s very easy to notice if the memory gets cleaned or not in Task Manager.
Event Handlers
Imagine you have the following situation: You have 2 windows. The first one has a text box inside. The second window (a child window) registers to the TextChanged event of the textbox in the first window. Now, since this textbox remains alive (because it’s in the main app window), the child window will remain alive in memory even if it’s closed.
public void RegisterEvent(TextBox textBoxFromParentWindow)
{
this.textBoxFromParentWindow = textBoxFromParentWindow;
textBoxFromParentWindow.TextChanged += new TextChangedEventHandler(textBoxFromParentWindow_TextChanged);
}
Fix
The fix for this is simple - just unregister the event when the child window closes.
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (chkUnregister.IsChecked == true)
{
this.textBoxFromParentWindow.TextChanged -= textBoxFromParentWindow_TextChanged;
this.textBoxFromParentWindow = null;
}
}
Events from static objects
If you register for an event that is declared in a static object, the memory will never be freed because the static objects always stays alive in memory.
MemoryHelper.StaticInstance.LeakingEvent += new EventHandler(StaticInstance_LeakingEvent);
Fix
Again, you can fix the memory leak by simply unregistering from the event:
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (chkCleanup.IsChecked == true)
{
MemoryHelper.StaticInstance.LeakingEvent -= StaticInstance_LeakingEvent;
}
}
Timers
The use of timers in a window is very dangerous because when the window closes, the timer continues to be alive and ticking, thus keeping the window it’s in alive. Simple demonstration for this is the following piece of code:
timer = new DispatcherTimer();
timer.Tick += new EventHandler(timer_Tick);
timer.Interval = TimeSpan.FromSeconds(1);
timer.Start();
Try the sample application, to see that if you don’t explicitly stop the timer, it’ll continue ticking even when the window is closed.
Fix
To fix this memory leak you’ll have to explicitly stop the timer when you don’t need it anymore:
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (chkCleanup.IsChecked == true)
{
timer.Tick -= timer_Tick;
timer.Stop();
}
}
Now the memory is released when the windows is closed.
Data binding
Sometimes when using data binding, you can cause a memory leak. An example situation: You have a container (for example a stack panel) and inside it you have a text block that binds to a property of the container. Memory leaks occur when the property of the source object is not a DependencyProperty. For example – stackPanel.Children.Count. If you bind to a dependency property, the memory is properly cleaned up.
Leaky data binding:
<TextBlock Text="{Binding ElementName=layoutRoot, Path=Children.Count}" Margin="5 0" x:Name="leak"/>
Safe data binding:
<TextBlock Text="{Binding ElementName=layoutRoot, Path=ActualWidth}" Margin="5 0" />
The ActualWidth property is a Dependency Property, so it does not cause a memory leak.
Fix
You’ll have to clear the data bindings for non-dependency properties when you don’t need them anymore:
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
if (chkCleanup.IsChecked == true)
{
BindingOperations.ClearBinding(leak, TextBlock.TextProperty);
}
}
Changing the Text property of a text box
The TextBox control in WPF keeps a undo stack in memory which can cause problems when the text is changed lots of times. In the sample application there’s a button which starts changing the Text property 10 000 times. If you look at Task Manager you’ll notice that memory usage increases exponentially, and when you close the window, the memory is not released.
Fix
You can avoid this problem by limiting the number of undo operations that are kept in the stack.
textbox.UndoLimit = chkLimit.IsChecked == true ? 0 : int.MaxValue;
In the sample app you can see that when you check the “Limit” checkbox that changes the UndoLimit to 0, which essentially fixes the large memory usage, but of course disables the undo operations on the text box.
Download Sample
You can download the sample application and test the cases yourself.