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>> _handlerRegister(){_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”.
No comments:
Post a Comment