Preventing Memory (and Time!) Leaks in WPF with Weak Events

Normally in .NET the garbage collector eliminates any concern we might have regarding memory leaks.  However, there are certain cases where “lost” objects may be retained because references to them still exist.  One possible mechanism for this is event wiring.

Imagine the case where a host component, like a main application window, contains an object which can notify of changes.  The main window instantiates a child component, and sets a property on the child to the observable component.  The child component then begins to listen for changes.  This adds a reference to the child window to the listener collection of the observable component.  Since the observable component is long-lived, that reference will persist.  If the child component is created and destroyed many times, a noticeable memory leak may result.

Let’s create a simple main and child components (in this case, windows). The main window will own the reference to the observable item. The child window will accept it as a property, and listen for changes on it. Very important to note that these components do not need to be windows; they can be any created and destroyed component.

Here’s a framework for the main window:

public partial class MainWindow : Window
{
   private ObservableCollection items;

   public MainWindow()
   {
      InitializeComponent();
      items = new ObservableCollection();
      items.Add(5);
      items.Add(7);
   }

   public void ShowCloseOtherWindow()
   {
      Window1 w = new Window1 { Items = items };
      w.Show();
      w.Close();
   }
}

And here’s a framework for the child window. Notice I am omitting the event wiring, which will occur in the setter. This is the point of difference and where weak events will come into play. The assumption is some control is databound to the Sum property and will receive notifications of changes via Window1’s PropertyChanged event.

public partial class Window1 : Window, INotifyPropertyChanged
{

   private ObservableCollection _items;
   public ObservableCollection Items
   {
      get
      {
         return _items;
      }
      set
      {
         _items = value; 
         // TODO: Wire this to notify collection changed
      }
   }

   public int Sum { get; protected set; }

   void _items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
   {
      Sum = Items.Sum();
      if (PropertyChanged != null)
         PropertyChanged(this, new PropertyChangedEventArgs("Sum"));
   }

   public event PropertyChangedEventHandler PropertyChanged;
}

For testing purposes, we can rig up a simple timer on the main window which will create and close many child windows. The timer will simply invoke ShowCloseOtherWindow every, say, 250 ms or so. We can then monitor the memory usage of the application with different notification techniques. To monitor memory usage, I’m using perfmon on the specific process. To monitor performance, I’m using a Stopwatch instance around a call to .Add in the main window. This Add occurs every 30 seconds. When the Add (or any other change) occurs, the observable collection notifies all listeners. The time taken to notify all listeners is monitored by the stopwatch.

To establish a baseline, I will start with the empty implementation of the Items setter. This is obviously unacceptable in practice, since it does not result in changes (or even the original value) of the Items being exposed to the Sum calculator.

In this diagram, overall app memory (blue) and time to add item (red) are indicated. Overall memory usage increases slightly over time (as expected), since items are continually added to the collection. Since the collection is never actually listened to or processed in any way, time to add stays relatively flat.
NoEventListen - Copy

A Naive Implementation

Now assume we want Window1 to listen to the collection for changes, and when changes occur, to recalculate the value of Sum. We can modify the setter of Items:

if (_items != null)
{
   _items.CollectionChanged -= _items_CollectionChanged;                    
}
_items = value;
_items.CollectionChanged += _items_CollectionChanged;

This seems very reasonable: it ensures that the items collection can be changed, and that extraneous events will not cause a recalculation. Nevertheless, this implementation hides a dangerous flaw: By adding the listener, the items collection now has a reference to the component (in this case, an instance of Window1). If the component is intended to be discarded, the garbage collector will not collect it because a reference still exists (from the items collection). This causes a memory leak if many instances are created and then discarded. Furthermore, since the “zombie” components are still listening for changes and still recalculate the Sum on changes, as more and more instances are created (and zombified), the processing overhead of adding values will increase dramatically, causing a time leak.

Let’s run the program again, with the same instrumentation on memory (blue) and time (red). Notice how both skyrocket quickly (time usage even increases polynomially). The flat line of memory at the end is where the program crashed!
EventListenDefault

Hopefully you can see how aggressive this kind of memory/time leak can be and how insidiously difficult to locate it it might be in a large and complex program!

How to Fix

The “obvious” solution is to use IDisposable and dispose of the object, having the Dispose method remove the event listener. In some cases, disposing of objects is tricky and we want to allow the garbage collector to work. For the remainder of this article, I will assume we want to find a garbage collectable solution.

Problems with garbage collection can sometimes be addressed using “weak references”. A weak reference tells the garbage collector not to count that particular reference when considering whether an object can be freed or not. Thus, if we modify the event handler above so that it is a weak reference, the garbage collector will still be able to collect it, and the problem (both time and space) will go away.

However, this is not as trivial as it seems. Although .NET has a WeakReference class, this class only supports object references, not delegates (and by extension, not event handlers). What we really need is a “weak event” mechanism.

Several solutions exist. In general, an intermediate static object which maintains the weak references and dispatches events as appropriate is needed. In many cases, developers have needed to write these classes themselves. For example, see the detailed Weak Event Handlers blog entry by Steven Hollidge.

In WPF, Microsoft has provided a framework of this sort in the .NET library through WPF weak events. This implementation makes it very easy for WPF developers to use weak events. Although the documentation on weak events is somewhat confusing, the generic WeakEventManager is sufficient and can be easily used in place of a traditional unsubscribe/subscribe model. We modify the Items setter as follows:

if (_items != null)
{                    
   WeakEventManager<ObservableCollection<int>, NotifyCollectionChangedEventArgs>.RemoveHandler(
      _items, "CollectionChanged", _items_CollectionChanged);
}
_items = value;                
WeakEventManager<ObservableCollection<int>, NotifyCollectionChangedEventArgs>.AddHandler(
   _items, "CollectionChanged", _items_CollectionChanged);

There is no need to modify the event handler or write any other boilerplate code.

The result? Memory usage and time increase slowly as items are added to the collection (which is exactly the expected behavior). The time increase is due to the calculation of sum which now occurs, whereas in the first graph, since the event handler was never wired, no sum calculation ever occurred.
EventListenWeak

Download the complete WPF Weak Reference demo source code, including both techniques and instrumentation.

A comprehensive article dealing with weak events in all their facets is available by Daniel Grunwald.

Comments are closed.