Andy Kutruff

Blog

High Performance Property Changed Weak Event Notifications for C#

Posted by Andy Kutruff on March 6, 2009

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:

WeakDictionary<INotifyPropertyChanged, WeakEventBridge>

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:

WeakPropertyChangedEventManager.Register(
                    person,
                    "Age",
                    this,
                    (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.

To unregister:

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:

WeakPropertyChangedEventManager.RemoveCollectedEntries();

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.

About these ads

7 Responses to “High Performance Property Changed Weak Event Notifications for C#”

  1. sacha said

    After reading this and loads of other articles on weak events lately I am a tad confused. Are you saying that normal objects that implement INotifyPropertyChanged and use standard WPF bindings are not weak by default.

    Is this not what System.ComponentModel.PropertyChangedEventManager is all about.

    Also what about RoutedEvents, are these weak in nature (under the covers). I can get no answer on this.

    I found this excellent article

    http://diditwith.net/PermaLink,guid,aacdb8ae-7baa-4423-a953-c18c1c7940ab.aspx

    Which is what we use for any CLR events we want to raise, we created an extension method for EventHandler for MakeWeak as the author suggests, and it works very well, with auto un-registration also.

    Could you let me know your thoughts on RoutedEvents in WPF.

  2. GreetingS!
    Interesting points! I was actually thinking about this toppic last night and this morning (parrticularly how to incorporate it into my own blog). Thanks for the tips, bro!

  3. Samuel said

    Interesting post!

    Sacha, from what I have read it is my understanding that weak references are indeed used in WPF’s standard bindings. From what I read I think the problem here is the performance issue on constantly checking the weak reference list in order to dispose of weak references.

    Andy, just a quick comment on

    “I plan to post quite a bit about the internals if I don’t get lazy”

    I would love to see some more! :)

  4. chha said

    I solved the problem of collecting the dead references in Nick Guerrera’s WeakDictionary like this:

    I added an additional property called AutoCleanUpInterval as TimeSpan and a private variable that holds the LastCleanUpTime, and check during every manipulation of the dictionary (Add, Remove, GetEnumerator etc.) whether the timeout is reached and if it is, then I call the RemoveCollectedEntries and set the LastCleanUpTime variable to now. This slows down performance a little bit but is much better than calling the RemoveCollectedEntries every time and does not need an own thread to do the cleaning.

    Best regards
    Chris

  5. Hello, after reading this awesome piece of writing i am as well happy
    to share my familiarity here with friends.

  6. Thanks for the auspicious writeup. It in reality was a leisure account
    it. Glance complex to far delivered agreeable from you!
    However, how could we keep in touch?

  7. Hey, I think your blog might be having browser compatibility issues.
    When I look at your blog site in Chrome, it looks fine but when
    opening in Internet Explorer, it has some overlapping.
    I just wanted to give you a quick heads up! Other then that, wonderful blog!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: