A discussion about Rich Internet Applications, and user experience focusing on architecture, design patterns and methodologies.

Friday, June 4, 2010

Leak proof event bus using MEF

I’ve been trying to implement my own “Event Bus” (Pub/Sub pattern) using MEF since it seems natural to export an event, or a delegate.  The problem is that a reference on one object to a delegate on another object could cause memory leaks.  (See Weak Events in C# by Daniel Grunwald   http://bit.ly/chKG7W).

Following Daniel Grunwald’s article, I considered all the possibilities there and finally settled on “Weak Reference to Delegate”.  This is simply an Event wrapper that uses a weak reference to store each delegate. 

        List<WeakReference> handlers = new List<WeakReference>();
        public event EventHandler<TEventArgs> Event
        {
            add
            {
                handlers.RemoveAll(wr => !wr.IsAlive);
                handlers.Add(new WeakReference(value));
            }
            remove
            {
                    handlers.RemoveAll(wr =>
                                           {
                                               EventHandler<TEventArgs> target = (EventHandler<TEventArgs>)wr.Target;
                                               return target == null || target == value;
                                           });
            }
        }


As Daniel states though, this alone will cause the delegates to be garbage collected, even though the delegate’s source is still referenced.  The delegate’s source must maintain a hard reference to the delegate itself.  In terms of the pub/sub pattern, the subscriber needs to create a reference to the event handler that is being added to the published event.



private WeakReferenceToDelegate<EventArgs<TParam>> _eventReference = new WeakReferenceToDelegate<EventArgs<TParam>>();
EventHandler<EventArgs<TParam>> _handler
Register()
{
    _handler = Handler;
    _eventReference.Event += _handler;
}
private void Handler(object sender, EventArgs<object> e)
{
    ...
}


That is a lot of obscure code to subscribe to an event, so I set about encapsulating the logic.  The resulting code looks like this:



    public class Publisher
    {
        [Export("PublisherNotification")]
        public Notification<Object> Notification { get; set; }
        public Publisher()
        {
            Notification = new Notification<Object>();
        }
        public void PublishNotification()
        {
            Notification.Publish(null);
        }
    }
    public class Subscriber
    {
        private NotificationSubscription<object> _subscription;
        public Subscriber()
        {
            _subscription = new NotificationSubscription<object>("PublisherNotification", Handler);
        }
        private void Handler(object sender, EventArgs<object> e)
        {
            //do something
        }
    }


The notification object is just a facade for WeakReferenceToDelegate.



    public class Notification<TParam>
    {
        private WeakReferenceToDelegate<EventArgs<TParam>> _eventReference = new WeakReferenceToDelegate<EventArgs<TParam>>();
        internal void Subscribe(EventHandler<EventArgs<TParam>> handler)
        {
            _eventReference.Event += handler;
        }
        public void Publish(object source, TParam param)
        {
            _eventReference.FireEvent(source, new EventArgs<TParam>(param));
        }
    }


 



The notification subscription object wraps the “Listener” logic.



 



    public class NotificationSubscription<TParam>
    {
        private EventHandler<EventArgs<TParam>> _handler;
        public NotificationSubscription(string notificationName, EventHandler<EventArgs<TParam>> handler)
        {
            _handler = handler;
            Notification<TParam> notification = ModuleService.Container.GetExportedValue<Notification<TParam>>(notificationName);
            notification.Subscribe(_handler);
           
        }
    }


If I tried to import the Notification object, a memory leak would occur.  I suspect that satisfying imports on an object creates a reference to that object which resulted in the subscription object living longer than the subscriber.  I used manual composition instead but that means I needed to locate the CompositionContainer.  I used a static reference to the container for this (ModuleService.Container).



The listener keeps a hard reference to the delegate.  The listener should only be referenced by the delegate’s source to prevent memory leaks. 



I have checked all of this in to a new version of the my Refract library that is dependent on MEF.   You can find it http://refract.codeplex.com/.  This particular code is in $\Code\Trunk\Refract\Refract\Event along with an implementation of a published function called “Query”.

About Me

My photo
Toronto, ON, Canada
I am a technical architect at Navantis, with over 12 years experience in IT. I have been involved in various projects, including a Hospital information system called CCIS for which my team received the 2007 Tech-Net innovation award. I have been working with Silverlight since beta 1, and am very keen on practically applying this technology and WPF to line of business applications.