WeakAction, or delegates with a weak reference to their target

Abstract: Thoughts on implementing an Action-type delegate that holds only a weak reference to the target object.

Background

I recently started evaluating MVVM libraries for my client’s project. Though I often share parts of my own unpublished library with a client I had inherited this project and it already had it’s share of commonly-used MVVM types. What it lacked, and I added, was a quickie "message bus" implementation. Not the distributed, enterprise-class sort of message bus, but an intra-app messaging service that decouples senders from receivers. It’s the message bus that I really wanted to replace with a more robust, tested version that must surely already exist in one of the established MVVM libraries.

As luck would have it the first library I downloaded touted a messaging service but, as is too often the case, you get what you pay for. After perusing the code for a bit I could see that the messaging functionality wasn’t thread-safe, a feature I require. My app is a multi-threaded WPF app with both managed and native threads and takes pains to keep work off the UI thread in order to achieve smooth and somewhat real-time rendering of data. Wrapping the library’s messaging code for synchronization would be easy, but it doesn’t feel like fine enough granularity of locking for good performance.

Weak Action is Weak

Another driver in looking for an established library is that my quickie message bus implementation holds references to subscribed listeners. Not really a problem unless a listener fails to unsubscribe, in this case the listener will never be garbage collected. I consider that an undesirable situation. So I took a look at the above library’s implementation of a weak action; I quickly saw that this code used not a weak reference but a strong reference; it suffered the very problem it purported to fix. FYI, I did verify that the problem is known to the library’s author.

Now, at this time I haven’t finished evaluating libraries, and this was a bit of a rough start. Maybe I’ll just set aside some time to come up with a solution myself. Like… now.

An Elegant Solution (maybe…)

The problem I’d like to solve is that Action holds a reference to the target object of non-static delegates, preventing that target from being GC’d. The most elegant solution would allow me to simply replace my use of Action and Action<T> with an implementation that holds only a weak reference to the target. And here it is:

   1: static Action CreateWeakAction(Action action)

   2: {

   3:     if (action == null)

   4:         throw new ArgumentNullException("action");

   5:  

   6:     // If it's a static delegate there's no need to create a weak reference.

   7:     if (action.Target == null)

   8:         return action;

   9:  

  10:     Type type = action.GetType();

  11:     WeakReference targetRef = new WeakReference(action.Target);

  12:     MethodInfo method = action.Method;

  13:  

  14:     return () =>

  15:         {

  16:             // Don't reference 'action' in this lambda expression.

  17:             object target = targetRef.Target;

  18:             if (target == null)

  19:             {

  20:                 // Target was GC'd; don't do anything. (Ick.)

  21:             }

  22:             else

  23:             {

  24:                 Delegate.CreateDelegate(type, target, method).DynamicInvoke();

  25:             }

  26:         };

  27: }

Using CreateWeakAction is pretty easy:

   1: Action action = CreateWeakAction(() => receiver.DoSomething(42));

   2: // Time passes...

   3: action();

Now the target (‘receiver’ in the above example) is free to be collected, but I have a new Action delegate returned from CreateWeakAction that will not be collected unless I release my reference to it. More than that, I have no way of telling whether the delegate I’m holding no longer has a live reference to the target. I could conceivably change my Action to a Func<> that returns an indicator of whether the target was GC’d, but that would require me to actually call the delegate to determine its state. And I would lose the elegant drop-in replacement I was hoping for.

A More Measured Solution

Setting aside my personal desire for elegance solutions, the actual requirements I’m looking to satisfy in my message bus are twofold: 1) don’t hold a reference to the target listener and 2) don’t hold unnecessary objects in the heap. A weak action implementation that satisfies these needs might look something like this:

   1: class WeakAction

   2: {

   3:     public WeakAction(Action action);

   4:     public bool IsTargetAlive { get; }

   5:     public WeakActionInvocationResult Invoke();

   6: }

This would allow me to create an instance of my weak action, invoke it’s behavior when needed, and check whether the target is still alive so that I can cull stale instances from my message bus implementation. And, thanks to an uninterrupted morning at my local coffee joint I have what I wanted. You may note that I’ve made an abstraction for invoking instance delegates; this makes that code more easily reusable for generic implementations (e.g., WeakAction<T>).

   1: internal class WeakAction

   2: {

   3:     private readonly InvocationAgent _instanceAction;

   4:     private readonly Action _staticAction;

   5:  

   6:     public WeakAction(Action action)

   7:     {

   8:         if (action == null)

   9:             throw new ArgumentNullException("action");

  10:  

  11:         if (action.Target != null)

  12:         {

  13:             _instanceAction = new InvocationAgent(action);

  14:         }

  15:         else

  16:         {

  17:             _staticAction = action;

  18:         }

  19:  

  20:         Debug.Assert(_instanceAction == null || _staticAction == null);

  21:         Debug.Assert(!(_instanceAction == null && _staticAction == null));

  22:     }

  23:  

  24:     public bool IsTargetAlive { get { return IsStatic || _instanceAction.IsTargetAlive; } }

  25:  

  26:     private bool IsStatic { get { return _staticAction != null; } }

  27:  

  28:     public WeakActionInvocationResult Invoke()

  29:     {

  30:         if (_instanceAction != null)

  31:             return _instanceAction.Invoke();

  32:  

  33:         _staticAction();

  34:         return WeakActionInvocationResult.Invoked;

  35:     }

  36: }

Note that I’ve made an optimization for static delegates—there’s no need to incur the cost of dynamic invocation in that case because a static delegate has no target, therefore it won’t prevent anything from being GC’d.

Now imagine we have a messaging implementation that needs to track listeners with Action delegates but would rather not keep a reference to listeners forever if they happen not to unsubscribe. We can now create an instance of WeakAction and use it to invoke the listener’s Action when we wish; we can also query the WeakAction on occasion to determine whether it still has a live listener.

Finally here are the missing parts to make it work, the interesting bits. What makes this work is pulling apart the delegate into its component parts, holding the target part with a  weak reference, and reassembling the parts into a delegate only when needed to invoke the original delegate. No reference to the original delegate is held.

   1: internal enum WeakActionInvocationResult

   2: {

   3:     Invoked,

   4:     Collected,

   5: }

   6:  

   7: internal class InvocationAgent

   8: {

   9:     private readonly Type _type;

  10:     private readonly WeakReference _targetRef;

  11:     private readonly MethodInfo _method;

  12:  

  13:     public InvocationAgent(Delegate action)

  14:     {

  15:         Debug.Assert(action.Target != null, "Expected a non-static delegate");

  16:  

  17:         _type = action.GetType();

  18:         _targetRef = new WeakReference(action.Target);

  19:         _method = action.Method;

  20:     }

  21:  

  22:     public bool IsTargetAlive { get { return _targetRef.IsAlive; } }

  23:  

  24:     public WeakActionInvocationResult Invoke(params object[] args)

  25:     {

  26:         object target = _targetRef.Target;

  27:         if (target == null)

  28:             return WeakActionInvocationResult.Collected;

  29:  

  30:         // Don't keep a reference to this delegate.

  31:         Delegate.CreateDelegate(_type, target, _method).DynamicInvoke(args);

  32:         return WeakActionInvocationResult.Invoked;

  33:     }

  34: }

I’ll leave it as an interesting challenge to the reader to implement WeakAction<T>. Here’s what it might look like:

   1: class WeakAction<T>

   2: {

   3:     public WeakAction(Action<T> action);

   4:     public bool IsTargetAlive { get; }

   5:     public WeakActionInvocationResult Invoke(T t);

   6: }

Grab a cup of coffee and have at it. :) Please note: I’ve tested only on the desktop CLR, not Silverlight or Phone, so caveat emptor.

Moving On…

It’s that time again, when one contract ends and another begins. I’m never able to wrap up all the loose ends that I’d like to.

Yet it’s a bit renewing, helping one project through RTM and SP1, then picking up and beginning fresh on something totally different with a completely different set of people. It’s interesting how from one project to the next people vary so much. It’s like they’re wired differently. :)

Aside from the pleasure of meeting and working with new people, I also enjoyed this view from my desk in recent months. (Sorry about the cell phone picture quality.) Can you find the Space Needle?

The view from my desk

If experience is any indication my desk at my next assignment will likely face a beige wall in building 41 instead of downtown Seattle. But it involves some pretty cool software. Oh, well, you take the good with the bad. :)

Technorati tags: ,