I’ve been doing a lot of work on Continuous LINQ (CLINQ), and by that I mean I’ve written 90% of version 2.0. CLINQ is being developed for a large scale and real life WPF application that must be robust and high performance. I plan to post quite a bit about the internals if I don’t get lazy. I’ve had to put in so much trickery and ran into so many esoteric things that I figure passing the learning on would be worthwhile.
Kevin Hoffman, the project administrator, and my partner in crime for CLINQ posted about my original implementation for weak events early on in the development of CLINQ 2.0.
In a WPF application, INotifyPropertyChanged is king. All objects must implement it in order to be consumed by the UI. There’s lots more to discuss about this interface that I’ll save for other blog posts. For now, what’s important is how WPF utilizes this interface. Whenever a property on an object is data bound to control in WPF, the control must know when the value of that property changes in order to update the UI. This may sound straight forward, since INotifyPropertyChanged exposes the PropertyChanged event. WPF must just subscribe to PropertyChanged and be on its merry way, right? I’m sure the WPF dev team wished it were that easy, as we all did, when we realized that the C# event model is a recipe for memory leaks. Whenever you subscribe to an event, you must unsubscribe from that event or else the subscriber runs the risk of leaking. For more information, and an excellent write up of this problem and solutions see this article on code project: Weak Events in C#. I used one of the solutions here for CLINQ, but I’ll get to that in a minute.
To avoid event based memory leaks, the WPF team came up with the WeakEventManager pattern. For listening to PropertyChanged events, they specifically implemented the PropertyChangedEventManager. Any system that must manage subscriptions is basically a smart cache, and two level lookup table. A subscriber wishes to listen to for a specific property change on a specific object. For example, imagine a TextBlock that is bound to the Age property on an object of type Person. The manager first needs to keep a lookup table keyed off the monitored object to another table that is keyed off the name of the property. You can think of it like this:
Dictionary<INotifyPropertyChanged, Dictionary<string, List<IWeakEventListener>>>
This is all well and good but it leads to the requirements of PropertyChangedEventManager, where each listener must implement the IWeakEventManager interface, which is awesome. (By awesome, I mean not. Why should your objects be concerned with such things?)
Now that we have this cache which stores each entry as a WeakReference, we need to somehow clean up this cache as objects get garbage collected. This is where things start sucking. For CLINQ we could be monitoring thousands of property changes per second, and if the event management system cleans up too often, then perf goes down the drain. When I loaded our app in a profiler, I noticed that 30% of our CPU time was being spent in the PropertyChangedEventManager’s cleanup code. It appears that subscribing or unsubscribing from any single object causes them to “schedule” a clean up. This means PropertyChangedEventManger does a BeginInvoke on the UI thread that walks the whole cache looking for dead WeakReferences. For WPF alone it appears to do the job okay, but as CLINQ’s bread and butter is to listen to specific property changes on specific objects that are being added and removed from collections constantly, PropertyChangedEventManager chokes and dies.
Before I describe the replacement, I have two people to thank: Daniel Grunwald on the aforementioned Code Project article on weak events, and the author of the WeakDictionary, Nick Guerrera.
The replacement is called WeakPropertyChangedEventManager, and handles things quite a bit differently. First, there is still a cache:
This keeps track of what objects are being monitored for property changes. Each object has one WeakEventBridge which is a tiny wrapper that does two things. First, it follows the Reusable Wrapper approach from Grunwald, in that WeakEventBridge could technically leak. This only happens the property changed event never fires again and the listeners have not unsubscribed. Lame? Not really. PropertyChanged fires all the time, and if a listener has been garbage collected the WeakEventBridge will clean up its subscriptions very quickly. Also, the listeners still can still explicitly unsubscribe which is what CLINQ does most of the time anyway. Granted, if you have one object in the system with a million subscriptions combined with not explicitly unsubscribing listeners and that monitored object’s properties never change, you got a big leak. Why one would have this many objects monitor property changes on something that never changes is probably a bad idea anyway… Regardless, there is only ever one instance of the WeakEventWrapper per object in the entire system, which from a footprint perspective is tiny. Hopefully, you’re not lost at this point, but fear not as here comes the API:
(me, sender, args) => me.OnPropertyChanged(sender, args));
First, ignore the hard coded string for the property name in the example as this is bad form. (CLINQ analyzes a lambda expressions to extract property names.) This call says: listen for changes to person.Age, and call this.OnPropertyChanged just like normal events in C#. The reason for the lambda is to prevent any type of closure from being created as this would lead to a strong reference back to “this.” When person.Age changes the lambda will be called and get this passed in as the me parameter. Yes, it is a bit weird, but I got this trick from Grunwald’s work and it’s seriously slick.
WeakPropertyChangedEventManager.Unregister(person, "Age", this);
If you always call these two methods then you are good to go. However, what would be the point of going to all this trouble then? There’s a method for cleaning up dangling weak references just like the original PropertyChangedEventManager:
I have wrestled with whether or not to put in a scheduled cleanup task like the original but with way less frequency. However, considering the low overhead of these “leaks,” I’m going to leave it to the clients to call when they feel it is necessary. When the 4.0 version of the CLR comes out, I hear it will be able to notify when the garbage collector is making a pass, which would be a perfect time to make this call. Otherwise, I may add a dirty timer with configurable interval and thread priority. Regardless, the overhead of dealing with property change monitoring has been drastically reduced by using this class. If you would like to check it out, grab Continuous LINQ and pull up a chair to the ContinuousLinq.WeakEvents namespace.
If you found this article useful or lacking, please leave a comment so I can better target and improve my posts.