Weak Delegate WPF Observable Collection
Primary Problem Statement and Illustration
A common problem with production-scale WPF applications is memory leaks of GUI objects. This occurs due to the implementation of the Observable pattern in WPF's key INotifyCollectionChanged interfaces. This interface permits a data model object or collection to notify an observer whenever a collection changes with an intuitive and efficient interface. The leak happens whenever a WPF GUI object outlives the data model object it is displaying. For example, given a simple data model object that looks like this:
public class ExampleProvider : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Name { get; set; }
public ObservableCollection<string> GroceryItems { get; set; }
}
Let's also assume there is a WPF window (could be a user control too, doesn't matter) that displays this object with a TextBox bound to Name and an ItemsControl bound to GroceryItems. At InitializeComponent, the ItemsControls checks GroceryItems to see if it supports INotifyCollectionChanged and then installs a subscriber on that event so it is notified of any change to the collection. If you then call GroceryItems.CollectionChanged.GetInvocationList() to check the list of installed callbacks, you see one delegate that looks like this:
Method {Void OnCollectionChanged(System.Object, System.Collections.Specialized.NotifyCollectionChangedEventArgs)} System.Reflection.MethodInfo {System.Reflection.RuntimeMethodInfo}
Target {System.Windows.Data.ListCollectionView} object {System.Windows.Data.ListCollectionView}
A C# delegate object has only two members: Method and Target. These determine how to make the requested call. Method identifies the method being called. Target identifies the THIS object to use for that method invocation. Target can be null if this is a static or anonymous method that doesn't reference any instance members. The important point here is that this delegate has a strong (normal) reference to the ItemsControl! Because the ItemsControl is contained by the window object - in other words, in memory, the window object has a list of child objects and the ItemsControl is in that list - the window cannot be garbage collected until this reference to it is removed. Well, that's no problem, you may say. When the window is closed, it will unsubscribe from the data model. This is incorrect: WPF GUI objects do not disconnect event handlers when they are destroyed, because the overhead of doing so is significant. OK, so, what does that mean? Every single time you create one of these windows, it is stuck in memory until your data model is shutdown. If your data model lasts the lifetime of your program, as is common, that means every single window will stay in memory forever. This makes no sense, but once you realize that the GUI objects never disconnect their CollectionChanged event handlers, well, that's what happens.
This is not an issue for WPF's PropertyChanged handlers because the WPF framework creates and registers weak delegates itself (oblique reference here). It is only an issue for collections.
Secondary Problem
A second, unrelated issue with WPF is thread affinity. WPF GUI objects can only be used on their thread of creation. This can become an issue when, for example, you have a worker thread that updates a collection. The CollectionChanged notification then fires on the worker thread and you promptly receive a WPF exception from WPF's CollectionView GUI object stating that it can't be run from a non-GUI thread. The only fix for this is to Dispatcher marshal the call back to the GUI thread. However, the nature of multi-thread access to a shared [threadsafe] collection means that not all accesses are safe. In particular, if you remove objects from your collection or change them to different types on a worker thread, then a GUI thread can subsequently crash (due to a count mismatch or type mismatch). This is why the WPF team omitted automatic support to marshal these calls to the GUI thread (see question 6.3 here).
In NET 4.5, this has now been resolved by adding synchronization support so all GUI accesses of the object go through a lock object or user-supplied lock method. But I can't rely on NET 4.5 right now, so I have to roll my own solution as many others have done. The solution for this is presented here as well because it requires knowledge of the solution to the primary problem.
Potential Resolutions
There are only two ways to fix this:
1. Subscriber: Modify the WPF objects so they all disconnect their event handlers when the window is closed.
2. Event Provider: Modify ObservableCollection so it uses weak references somehow for all subscribers.
Unfortunately, we don't have the source to WPF and there's no word that the WPF team is going to address this anytime soon. Further, NET 4.5 adds support to resolve some instances of this problem using a variation of solution #2, so that appears to be Microsoft's direction for resolution. That brings us to solution #2. The NET framework supports a concept called a weak reference. This is a reference to an object that isn't considered a reference for garbage collection purposes. This is designed to let you hold a reference to an object that you need to use, but you don't want your reference to keep that object in memory. Weak references are designed for exactly this sort of problem.
To review, a callback in C# is a delegate object, implemented by System.Delegate. Delegate objects contain only two members: method and target. The method isn't an issue here since that's a reference to class type information that is already stuck in memory. There's only one instance of that anyway. So what we want to do is subclass System.Delegate to add a WeakReference member, use that instead of Target, and override Target to resolve the weak reference and then make the call. Conceptually, this is easy. However, we can't subclass System.Delegate, so we're stuck, right?
Specific Problem Statement
To fix this issue, we must meet the following requirements:
1. Convert a user-supplied delegate into a weak delegate, one that uses a weak reference to Target instead of a strong one.
2. Provide an ObservableCollection that uses weak delegates for CollectionChanged handlers
3. Install and remove handlers in the standard way, so this class is a drop in replacement.
This class should be a drop-in replacement for an existing ObservableCollection. Checking the class source, we see that it uses a virtual CollectionChanged event and calls a virtual OnCollectionChanged handler to raise the event, so our needs have been anticipated. That means meeting item #2 and #3 are straightforward. The real problem is #1, converting a delegate into a weak delegate.
Solution
Simple Weak Delegate
The very first solution that may occur to you is to simply keep a list of WeakReferences of delegates. In other words, it might seem easy to do this:
private List<WeakReference<NotifyCollectionChangedHandler>> actualHandlers;
public event NotifyCollectionChangedHandler CollectionChanged
add
actualHandlers.Add(new WeakReference(value))
remove
// Omitted for clarity, unimportant here
protected virtual void OnCollectionChanged( object sender, NotifyCollectionChangedEventArgs e)
foreach (var weakWrapper in actualHandlers)
{
NotifyCollectionChangedHandler actualHandler;
actualHandler = (actualHandler) weakWrapper.Target
if (actualHandler != null)
actualHandler.Invoke(sender, e);
}
However, this would be an immediate failure because the entire delegate will be cleaned up at the first GC because nothing effective references it. The only object referencing object is a WeakReference, so there is nothing holding the user's delegate in memory. So that won't work.
Slow Weak Delegate
Let's look into a key problem we have in solving this to better understand how to solve it. A key problem is that we can't override System.Delegate. But we need to add a member to System.Delegate to do this! So we can't change System.Delegate...Wait! Let's write our own delegate with our OWN target object. When our Invoke call is made, we'll be running in the context of our own object and we can access any state we need. We can then make the Invoke call as needed. This should solve it! Let's write it:
/// <summary> /// Weak delegate for a delegate following the standard EventArgs pattern: args are sender, EventArgs-descendant /// Works fully, but performance is bad since invocation is based on MethodBase.Invoke /// </summary> /// <typeparam name="T">An Event-Args derrived class</typeparam> public class WeakEventHandlerSlow<T> where T : EventArgs { private WeakReference targetReference; private MethodInfo methodToInvoke; private EventHandler<T> wrappedHandler; public WeakEventHandlerSlow( EventHandler<T> newHandlerToWrap ) { if (newHandlerToWrap.Target != null) targetReference = new WeakReference(newHandlerToWrap.Target); methodToInvoke = newHandlerToWrap.Method; wrappedHandler = InvokeWrappedHandler; } public void InvokeWrappedHandler( object sender, T eventArguments ) { // Is this an object member method? if (targetReference != null) { // Yes, resolve THIS and make the call, if THIS is still alive object targetObject = targetReference.Target; if (targetObject != null) methodToInvoke.Invoke(targetObject, new object[] { sender, eventArguments }); } else { // No, this is a static/anonymous method. Invoke without THIS. methodToInvoke.Invoke(null, new object[] { sender, eventArguments }); } } /// <summary> /// Casts constructor type to needed EventHandler[T] type to support easy syntax (event += new WeakEventHandlerSlow(userHandler)) /// </summary> /// <param name="weakWrappedHandler">The wrapped weak event handler to convert</param> /// <returns>A delegate that matches the signature of user handler that is being wrapped</returns> public static implicit operator EventHandler<T>(WeakEventHandlerSlow<T> weakWrappedHandler) { return weakWrappedHandler.wrappedHandler; } }
This solution works! We have our own object with a handler that meets NotifyCollectionChanged. We can then take apart the user's delegate, store the method and target separately, using a weak reference to the target. When our InvokeWrappedHandler is called, we simply call resolve Target and call the user's handler with it, if Target is still good. Now that we see the solution, it also doesn't look all that complicated, so maybe that's why we haven't heard much about this from the WPF team.
As it turns out, I'm working on a real-time data acquisition and graphing application, so we have to be very performance sensitive. CollectionChanged notifications are going to happen often in my app and servicing them quickly in the GUI will be important, so I did some performance tests to see how much slower this was than a normal user handler. I'm concerned about overhead of the call process, so my subscriber method is a trivial method that simply increments a variable and returns. I want to be comprehensive, so I've tested this with three different types of callbacks: a class instance method, a class static method, and a lambda method. For 1,000,000 calls, the results are as follows:
So my overhead is...200x, or 200,000%, if you like shockingly big numbers. The most important question to ask next is: Does this matter? This means it takes fractions of a fraction of a millisecond to fire the notification. Unless you're firing these notifications often or need to respond to them in the same picosend as the call, no, it's fine. Remember, the SLOW solution can fire 645,161 times per second, and it takes barely 2 HUNDRETHS of a MILLISECOND to fire. So before going any further, always ask yourself, is this good enough?
Fast Weak Delegate
My company is focused on accuracy and performance. Also, we will have hundreds of collections firing these events during real-time data acquisition. So for us, the answer is: yes, it does matter, make it faster. So how do we solve this? First, if you profile what the wrapper is doing, you'll find that all the slowness is in one place: methodToInvoke.Invoke. The overhead of wrapping the call, resolving the weak reference, and our small amount of logic are insignificant. It's all in Invoke. I learned a great deal researching this problem. First, this type of method invocation is one of the slowest in the NET framework. The following graph (borrowed from MSDN, here's the full article) illustrates the differences:
Before introducing these changes, the event notifier was using a delegate call (not MethodBase.Invoke), so we want our speed to be on the same order of that. Within 10x would be great, since this is after all a very fast, essentially atomic operation anyway. The key here is to recognize that we're trying to get to the same speed as a normal delegate call when we invoke the user's handler. Unfortunately, method invocation in NET is a bit of a black box. We don't have the source to MethodBase.Invoke or delegate's invoke, so it's hard to know why MethodBase.Invoke is slow. Well, OK, it isn't that simple: MethodBase.Invoke is doing much more, it does parameter type validation, autoboxing, and has to dynamically setup a call frame according to the signature requested. But compared to just dereferencing a function pointer in C++, it's complicated. What we want is to be able to create a delegate at runtime and then invoke it, as in:
var newDelegate = Delegate.CreateDelegate(MethodInfo, Resolved weak reference)
Accomplishing this goal is interesting. It requires using an open-instance delegate, knowledge of reflection, and careful generics. I did not develop this myself, however: the solution was provided by Dustin Campbell. His original implementation is available here. With this, the numbers for 1,000,000 notifications now look like this:
Now the overhead is 4.75x, well within requirements and no longer significant. My contributions to Dustin's solution were to provide the ObservableCollection wrapper using weak delegates, making this a drop-in replacement, and to cleanup and extend his original implementation. This implementation now supports static/anonymous methods, restructures the code for readability, and adds significant commentary explaining how things work and why things are done the way they are. Finally, I addressed one other problem, that of multithreaded access of ObservableCollections, which is relevant in my case. However, note that I did NOT synchronize access/modification to the ObservableCollection itself as the mechanism used may be implementation-dependant. For example, in my case, only the worker thread modifies the collection, and further, it is only appended to - no items are added or deleted. If you are unsure about the problems I am discussing here, wait for NET 4.5 and call BindingOperations.EnableCollectionSynchronization against your OC.
Callbacks to GUI Objects
Now we can return to the secondary problem discussed in the introduction. Dropping the solution in is not quite that easy due to the need to marshal GUI calls onto the GUI thread. This is easily resolved by checking the type of the delegate's Target before making the call. We can identify GUI objects by determining if Target is derived from System.Windows.Threading.DispatcherObject. If it is, then the delegate needs to be run on the GUI thread for that object. If not, the delegate can be called on the current thread. Because the Target in the weak delegate isn't the user's actual Target and is instead a WeakDelegate, we must crack that open, resolve Target, and then check it's type. This requires code like the following:
foreach (NotifyCollectionChangedEventHandler currWrappedHandler in actualCollectionChanged.GetInvocationList()) { // Is this a wrapped weak handler? if (currWrappedHandler.Target is WeakDelegate<NotifyCollectionChangedEventHandler>.IGetWrappedDelegateInfo) { // Yes. Target object is wrapped within this as a weak reference. Get a current reference to it. WeakDelegate<NotifyCollectionChangedEventHandler>.IGetWrappedDelegateInfo targetWrapped; targetWrapped = (WeakDelegate<NotifyCollectionChangedEventHandler>.IGetWrappedDelegateInfo) currWrappedHandler.Target; actualHandlerTarget = targetWrapped.Target; } else { // No. Target object is as the delegate specifies. actualHandlerTarget = currWrappedHandler.Target; } // Yes. Is the wrapped handler a class instance method on a GUI object? // Note that anonymous methods such as lambdas that don't reference member variables are effectively // static, so Target is null if (actualHandlerTarget is System.Windows.Threading.DispatcherObject) { // Yes. It must run on the thread it was created on. Run via GUI dispatcher. System.Windows.Threading.DispatcherObject guiThreadDispatcher; guiThreadDispatcher = (System.Windows.Threading.DispatcherObject) actualHandlerTarget; guiThreadDispatcher.Dispatcher.Invoke(currWrappedHandler, new object[] { this, eventArgs }); } else { // No, just invoke the method. Thread of invocation doesn't matter. currWrappedHandler(this, eventArgs); } }
With this, we can finally write a drop-in replacement for ObservableCollection<T>. That code is posted here, and is available as-is, with no warranty, explicit or implied. Click the link below - the arrow at the far right in the listing - to download it.