Tuesday, March 16, 2010

Weak Events

A while back I was having a discussion with my friend Tim Erickson (@imerickson) about problems surrounding GC and events in WPF.  Specifically, we were talking about problems with ICommand (WPF) implementations.  I mentioned to him that we have and use our own weak eventing pattern on my team.  I promised to write a blog post about it, but I’ve been slow doing so.  This is my attempt to make good on my promise.  Hopefully it’s not too awful.

The problem that weak events are meant to solve is one in which you an event source (the thing raising the events) that has a potentially longer lifetime than some of the corresponding event sinks (the things receiving the events).  In that case, normal .NET events (delegates) will cause the event source to keep the event sinks alive, unless some explicit event removal code is executed on the sinks to tell them to remove their event handlers from the source.  In many cases this is relatively easy to do, but in many it is very inconvenient to do so and the programmer would rather not “know” when the removal is necessary.

As an example, in our application, we have an editor grid that has bindings that act as an event source, while the grid acts as an event sink.  BTW, when I say “bindings” here, think of them as a ‘data source’ for the grid, not ‘bindings’ in the WPF sense.  These bindings are not really a problem, as the bindings are created and “attached” to the grid, and when they are “detached” from the grid, the grid removes any event handlers it has attached to the bindings.  In this case, we don’t need weak events.  However, underlying the grid bindings is a view model object (that is sort of an amalgamation of a view model and a data model object, but that’s an argument for another day) that has events to notify its subscribers if any of the contents of the VM have changed from anywhere in the UI.

In our UI, we can have multiple views on the same exact model object (think multiple views in MS Excel or MS Word) that each need to be notified if any one of the views causes a change in the model.  In this case, our model is an event source, and our bindings are an event sink.  Here’s the problem – our model lives “forever” (at least it lives as long as our application is ‘running’), yet the grid bindings get recreated when the user changes their view, moves to another item, opens a new view, or any number of other changes to the state of the UI.  In effect, we create MANY of these grid bindings objects throughout a single run of our application, and we wish to make no effort to track when they are no longer “in use”, since doing so is rather difficult and would litter our code everywhere with code to handle this tracking.

However, if we ignore the tracking, and just let the grid bindings “get released” by the grid, etc., the events that were hooked by the bindings from the model in order to get notifications of changes in the model will never get unhooked.  This will lead to the model “keeping alive” a huge number of these bindings objects and never releasing them, effectively creating a “memory leak” until the model is shut down.  Therefore, we had a choice – either hook the events and try to force appropriate shutdown in all the places it was needed, or find another solution that didn’t involve the bindings hooking events directly on the model.

Since we have total control over both the control and the underlying model, we had a lot of flexibility in our choice of solutions to this dilemma.  I investigated the .NET (WPF) model for weak events, as well as several other methods people had used on the net.  I even found a description from the venerable Jeffrey Richter in his CLR via C# book (and found it to have errors, as a matter of fact).  The WPF model is very general and is the way to go if you are building a control and don’t have control over the model that will be hooked to your control (or want to play nice in the WPF data binding way), but it leaves much to be desired in terms of the programming model.

The WPF weak event model is centered around the Microsoft.Windows.IWeakEventListener interface and the Microsoft.Windows.WeakEventManager base class.  For a discussion of some of the details, see http://blogs.msdn.com/nathannesbit/archive/2009/05/28/weakeventmanager-and-iweakeventlistener.aspx

For the most part, I agree with Nathan’s assessment of this class.  I do, however, believe that if you are writing controls to be consumed by applications out of your control, you effectively MUST follow this model (not mine or anyone else’s), since this is the model that is used throughout WPF.  While you could implement your own variation of this model, you still need to apply the general concepts that it does in any solution you come up with (i.e. the ‘endpoint’ providing the ‘weakness’ in the events is the event sink, not the event source).

For our application, we have adopted the ‘opposite’ pattern – that is, that the weak events should be sourced from the event source as weak, rather than having an intermediary in the weak event manager that handles turning ‘strong’ events from the model into ‘weak’ events (listeners) on the event sink.  We like this model because it hides the complexity of dealing with the ‘weakness’ of the events behind the facade of the event handler attachment points and ‘invocations’ in the model.  Our model basically works as follows: the event source provides a custom event implementation, and when sinks subscribe to this event, they are added to a weak event “multicast delegate-like” object that is private to the event source.  The subscribers don’t know that they are hooking to a weak event.  On the other hand, the model raises events using this “special weak event object” and the attached event sinks receive these messages if they are still alive.

There is one small wrinkle to our model.  Event handlers attached to weak events in this manner MUST be kept around by something else, or else they will get collected and will cease to receive events.  This isn’t a problem for us, since it’s exactly what we’d like to see happen, but there can be problems if one isn’t careful, as it will seem like events stop getting ‘caught’ by their intended targets, and you won’t know why.

So, now on to the details.  First off, we have a ‘WeakDelegate’ class, that is responsible for being a weak version of the Delegate class built in to the .NET framework.  It looks like the following:

   1: /// <summary>
   2: /// A "weak reference" version of a delegate.  This class is used 
   3: /// by the MulticastWeakDelegate type to support "weak events" 
   4: /// pattern in the system.
   5: /// </summary>
   6: /// <typeparam name="T">The type of delegate to return.</typeparam>
   7: public class WeakDelegate<T>
   8:     where T : class
   9: {
  10:     private readonly WeakReference _Target;
  11:     private readonly MethodInfo _Method;
  13:     /// <summary>
  14:     /// Constructs a weak delegate from the provided delegate.
  15:     /// </summary>
  16:     /// <param name="source"></param>
  17:     public WeakDelegate(Delegate source)
  18:     {
  19:         if (source == null)
  20:             throw new ArgumentNullException("source");
  21:         if (source.Target == null)
  22:             throw new ArgumentException(
  23:                 "Source points to a null target.  " 
  24:                 + "This is not usable as a weak reference.", 
  25:                 "source");
  27:         if (source.GetType() != typeof(T))
  28:             throw new ArgumentException(
  29:                 "Source is not the proper delegate type.  "
  30:                 + "The delegate type must match the type passed "
  31:                 + "as the type parameter 'T'",
  32:                 "source");
  33:         _Target = new WeakReference(source.Target);
  34:         _Method = source.Method;
  35:     }
  37:     /// <summary>
  38:     /// Returns the contents of the WeakDelegate as a proper 
  39:     /// Delegate object, or NULL if the
  40:     /// delegate's target has been collected already.
  41:     /// </summary>
  42:     public T Delegate
  43:     {
  44:         get
  45:         {
  46:             var obj = _Target.Target;
  47:             if (obj == null)
  48:                 return null;
  49:             return (T)(object)System.Delegate.CreateDelegate(
  50:                 typeof(T), _Target.Target, _Method);
  51:         }
  52:     }
  54:     /// <summary>
  55:     /// This property identifies if the delegate target has 
  56:     /// already been collected (false if so).  Note: there is a 
  57:     /// possible race condition with this property, so be sure to 
  58:     /// check the return value of the Delegate property for null 
  59:     /// even if IsAlive returned true.  This is just a quick way to 
  60:     /// identify as "definitely dead", it cannot be trusted not to 
  61:     /// change between calling it and calling the Delegate method!
  62:     /// </summary>
  63:     public bool IsAlive
  64:     {
  65:         get { return _Target.IsAlive; }
  66:     }
  68:     /// <summary>
  69:     /// Checks whether the contents of this weak delegate is the 
  70:     /// same as the delegate passed as a parameter.  If it 
  71:     /// contains the same target & method combination, then the 
  72:     /// function returns true.  If the contained "delegate" has 
  73:     /// already been collected, or doesn't match the passed 
  74:     /// delegate, it returns false.
  75:     /// </summary>
  76:     /// <param name="other">The delegate to test against.</param>
  77:     /// <returns>True if the delegate is the same as the 
  78:     /// target/method contained by this weak delegate, False if 
  79:     /// the weak delegate has already been collected or if the 
  80:     /// target/method differs.</returns>
  81:     public bool IsSameAs(Delegate other)
  82:     {
  83:         return ReferenceEquals(other.Target, _Target.Target)
  84:                && other.Method.Equals(_Method);
  85:     }
  86: }

The weak delegate has support for “wrapping” a normal .NET delegate (it, of course, copies the contents of the delegate and throws away the underlying delegate so that it can hold a weak reference to the target of the original delegate).  It also has support for asking about the validity of the weak reference, getting the delegate back (or null if it’s not alive anymore), and comparing this delegate to others (used in the ‘remove’ handler of events).

To implement weak events, we use these weak delegates in a similar way to the MulticastDelegate class’ use in C# (the class that is used for ‘event’ implementation).  For this, we have a MulticastWeakEvent generic class that allows us to get as close as possible to declaring an ‘event’ in C#, but the language won’t let us get quite there.  Here is our MulticastWeakEvent implementation:

   1: /// <summary>
   2: /// MulticastWeakEvent is the "weak event" pattern support object.  This class 
   3: /// allows classes that wish to provide weak event registration to do so.  One 
   4: /// of these delegate classes must be created for each event, and the class 
   5: /// must be given the event type (typically EventHandler&lt;TEventArgs&gt;) and 
   6: /// the type of the event arguments.
   7: /// </summary>
   8: /// <typeparam name="TEventType">The type of event to support.  This should 
   9: /// normally be the .NET 2.0 EventHandler&lt;TEventArgs&gt; type.</typeparam>
  10: /// <typeparam name="TEventArgs">The type of the event arguments object to use 
  11: /// in calls to this event.</typeparam>
  12: public class MulticastWeakEvent<TEventType, TEventArgs>
  13:     where TEventType : class
  14:     where TEventArgs : EventArgs
  15: {
  16:     private readonly List<WeakDelegate<TEventType>> _List;
  17:     private readonly Action<TEventType, object, TEventArgs> _InvokeAction;
  19:     private static Delegate _AsDelegate(TEventType handler)
  20:     {
  21:         return (Delegate)(object)handler;
  22:     }
  24:     /// <summary>
  25:     /// Constructs a new weak event delegate.  The action should simply call 
  26:     /// the delegate with the appropriate arguments.  For instance, the 
  27:     /// action should simply be a delegate that looks like 
  28:     /// <code>(d,s,e) =&gt; d(s,e)</code>.
  29:     /// </summary>
  30:     /// <param name="invokeAction">A delegate that invokes its first argument 
  31:     /// (the event delegate) passing the second and third arguments as the 
  32:     /// arguments to the invocation.</param>
  33:     public MulticastWeakEvent(Action<TEventType, object, TEventArgs> invokeAction)
  34:     {
  35:         if (!typeof(Delegate).IsAssignableFrom(typeof(TEventType)))
  36:             throw new InvalidOperationException(
  37:                 "TEventType must be a delegate type in MulticastWeakDelegate");
  39:         _List = new List<WeakDelegate<TEventType>>();
  40:         _InvokeAction = invokeAction;
  41:     }
  43:     /// <summary>
  44:     /// Invokes the event for all listeners, one at a time.
  45:     /// </summary>
  46:     /// <param name="sender">The sender to pass to the invocation.</param>
  47:     /// <param name="e">The event args for the invocation.</param>
  48:     public void Invoke(object sender, TEventArgs e)
  49:     {
  50:         lock (_List)
  51:         {
  52:             _List.RemoveAll(eh => !eh.IsAlive);
  53:             _List.ForEach(
  54:                 eh =>
  55:                 {
  56:                     var target = eh.Delegate;
  57:                     if (target != null)
  58:                         _InvokeAction(target, sender, e);
  59:                 });
  60:         }
  61:     }
  63:     /// <summary>
  64:     /// Adds a handler to the weak event invocation list.
  65:     /// </summary>
  66:     /// <param name="handler">The handler delegate.</param>
  67:     public void AddHandler(TEventType handler)
  68:     {
  69:         lock (_List)
  70:         {
  71:             _List.RemoveAll(eh => !eh.IsAlive);
  72:             _List.Add(new WeakDelegate<TEventType>(_AsDelegate(handler)));
  73:         }
  74:     }
  76:     /// <summary>
  77:     /// Removes a handler from the weak event invocation list (and compacts 
  78:     /// the list).
  79:     /// </summary>
  80:     /// <param name="handler">The handler delegate.</param>
  81:     public void RemoveHandler(TEventType handler)
  82:     {
  83:         lock (_List)
  84:             _List.RemoveAll(
  85:                 eh => (!eh.IsAlive) || eh.IsSameAs(_AsDelegate(handler)));
  86:     }
  87: }
  89: public class MulticastWeakEvent : MulticastWeakEvent<EventHandler, EventArgs>
  90: {
  91:     public MulticastWeakEvent(): base((d, s, e) => d(s, e))
  92:     {
  93:     }
  94: }

Now, given this class, we can show some examples on how to expose weak events.  Consider a class that has a “NameChanged” event of type EventHandler<NameChangeEventArgs>.  We can declare a subclass of MulticastWeakEvent (not strictly necessary, but helps with readability) as:

   1: public class NameChangeEventArgs: EventArgs
   2: {
   3:     public readonly string OldName;
   4:     public readonly string NewName;
   6:     public NameChangeEventArgs(string oldName, string newName)
   7:     {
   8:         OldName = oldName;
   9:         NewName = newName;
  10:     }
  11: }
  13: public class NameChangedEvent: 
  14:     MulticastWeakEvent<EventHandler<NameChangeEventArgs>, NameChangeEventArgs>
  15: {
  16:     public NameChangedEvent()
  17:         : base((d, s, e) => d(s, e))
  18:     {
  19:     }
  20: }

The main reason for declaring a special type for one of these events is that it helps avoid the silly (d,s,e) => d(s, e) crap that we’re forced to do so that we can avoid reflection costs in our code.  I couldn’t find a way around this without using reflection, so if someone has any ideas I’d be happy to hear about them (but don’t be surprised if I’m skeptical, since I fought this problem for a very long time).

Now, to create a model object that raises events of the type NameChanged (transparently), we can do the following:

   1: public interface IPerson
   2: {
   3:     string Name { get; }
   4:     event EventHandler<NameChangeEventArgs> NameChanged;
   5: }
   7: public class Person: IPerson
   8: {
   9:     private string _Name;
  10:     private readonly NameChangedEvent _NameChanged = new NameChangedEvent();
  12:     public Person(string nameAtBirth)
  13:     {
  14:         _Name = nameAtBirth;
  15:     }
  17:     public string Name
  18:     {
  19:         get
  20:         {
  21:             return _Name;
  22:         }
  23:         set
  24:         {
  25:             if(_Name == value)
  26:                 return;
  27:             var oldName = _Name;
  28:             _Name = value;
  29:             _NameChanged.Invoke(
  30:                 this, 
  31:                 new NameChangeEventArgs(oldName, _Name));
  32:         }
  33:     }
  35:     public event EventHandler<NameChangeEventArgs> NameChanged
  36:     {
  37:         add { _NameChanged.AddHandler(value); }
  38:         remove { _NameChanged.RemoveHandler(value); }
  39:     }
  40: }

I think this programming model is pretty clean, and it works well for what we’re doing.  The only caveat, as I said earlier, is that you absolutely must understand how GC works when working with these, or else you’ll have some very weird bugs related to event sinks disappearing in the night ;)  Allow me to give an example of the “event sink disappearing” problem in case you haven’t figured out what I’m referring to yet.  Consider the following code:

   1: public class PersonWatcher
   2: {
   3:     private readonly List<IPerson> _PeopleWithNameChanges = new List<IPerson>();
   5:     public IEnumerable<IPerson> GetPeopleWithNameChanges()
   6:     {
   7:         return _PeopleWithNameChanges;
   8:     }
  10:     private class Listener
  11:     {
  12:         private readonly List<IPerson> _TargetList;
  14:         public Listener(List<IPerson> targetList)
  15:         {
  16:             _TargetList = targetList;
  17:         }
  19:         public void HandleNameChanged(object sender, NameChangeEventArgs args)
  20:         {
  21:             _TargetList.Add((IPerson)sender);
  22:         }
  23:     }
  25:     public void ListenToPerson(Person personToListenTo)
  26:     {
  27:         var listener = new Listener(_PeopleWithNameChanges);
  28:         personToListenTo.NameChanged += listener.HandleNameChanged;
  29:     }
  30: }

The code above (while only an example) could, or at least some variation of it could very well arise in ‘real life’.  One might think they are decoupling the ‘listening’ aspect from the tracking aspect of the problem.  The problem with this is that since the Person sources ‘weak’ events, there are no objects that are ‘live’ that are holding strong references to the ‘listener’ instances created on line 27 of the snippet.  This means that as soon as the GC decides to do so, the listener will get collected, and you won’t receive the events you expected to receive.

One way around this is for something ‘live’ to hold on to the listeners until their work is done.  For instance, if the “PersonWatcher” class held on to a list of all its listeners, this would keep them alive.  Of course, this has the other problem of requiring you to release them at some point in order to allow them to get GCed, but that may not be such a big deal, considering you might know better when to do that release than you know when to ‘detach’ the event listeners.  In this particular example, I suspect it’s a wash.

Anyway, I hope you enjoyed this brief tour of our island of code surrounding weak references.  It has served us well thus far, even with the caveats I keep mentioning.  Until next time – happy coding!

1 comment:

Johnny Luo said...

Thanks Kelly, I found your this post is quite useful.