The eventing mechanism built into the .NET runtime makes it a piece of cake to implement a simple publisher - subscriber pattern (C# code follows). The example below revolves around the ‘payday’ event – which is called PayrollArrived. The event is published by the Employer class (the Employer announces when payday arrives). Each Employee (i.e. each instance of the Employee class) listens for the event – and is hence a subscriber to the PayrollArrived event.
// Publisher Class – EmployerPublisher – will publish the PayrollArrived event
public class EmployerPublisher
{
public event PayrollArrivedHandler PayrollArrived;
public void FireEvent(DateTime today)
{
if(PayrollArrived != null)
PayrollArrived(today);
}
}
// Subscriber class – Employee – interested in knowing when the PayrollArrived event occurs
public class Employee
{
public void OnPayrollArrival(DateTime day)
{
// Round up some friends and go spend the money!
}
}
// Hooking up the publisher and subscriber(s) in the client code
EmployerPublisher publisher = new EmployerPublisher();
Employee employee1 = new Employee();
Employee employee2 = new Employee();
publisher.PayrollArrived += employee1.OnPayrollArrival;
publisher.PayrollArrived += employee2.OnPayrollArrival;
// Now that the subscribers are hooked up, the event can be fired
DateTime now = DateTime.Now;
publisher.FireEvent(now.date);
However - there are a few gotchas to watch out for:
Gotcha 1 – One rotten apple – blocks the others. When multiple subscribers are present – what if one of the subscribers blocks the publisher so that it is effectively not available to other subscribers? This can easily occur if any one of the subscribers decide to do something lengthy (oh – like maybe run an infinite loop) in its event handler. How do we prevent such an occurrence? How do we prevent one rotten apple from spoiling the bunch?
Gotcha 2 - What if a subscriber is added while notification is in progress? Supposing the list of subscribers is being notified - and a new subscriber happens to come along (this is not a rare event - especially if any one of the subscribers happens to contain time-consuming code as in Gotcha 1) . What happens to the new subscriber? Does it get dropped? Do we add it? If so – where and when do we add it?
Gotcha 3 - Subscribers that fail to unsubscribe. A ‘good’ practice (maybe even a ‘best’ practice) in .NET eventing is to ensure that all subscribers eventually unsubscribe from their events – once they are done handling it. Failure to do so leads to potential memory leaks. This is actually one of the more common sources of memory leaks in .NET applications (see my blog on Common Sources of Memory Leaks in .NET Applications)
Gotcha 4 - Race conditions in the .NET eventing framework – leading to inconsistent handling of events.
Let us look at these one by one:
Gotcha 1 – One rotten apple….or the badly-behaved subscriber problem
The badly-behaved subscriber problem is best solved by working under the assumption that all subscribers will be badly behaved! So what is a publisher to do? One solution is to launch each notification on a new thread - that way even if a subscriber is badly behaved - it does not tie down the notification of other subscribers. Since the CLR in .NET provides a ready-made thread-pool, this is a seemingly simple solution. In fact, all one needs to do is use the built in support for asynchronous delegates (the event defined in the publisher class is really just a delegate). How would this look?
public class EmployerPublisher
{
public event PayrollArrivedHandler PayrollArrived
// This event firing is synchronous - i.e. the publisher instance is tied down until the subscriber finishes processing OnPayrollArrival
public void FireEvent(DateTime today)
{
if(PayrollArrived != null)
PayrollArrived(today);
}
// This is an asynchronous version of the same event-firing - just fire it - and do not worry about how long the subscriber takes - since subscriber processing
// is on a different thread.
public void FireEventAsynchronous(DateTime today)
{
if(PayrollArrived != null)
PayrollArrived.BeginInvoke(today, null, null);
}
}
We are almost there. The only problem with the above code is a C# restriction - if you want to call BeginInvoke, your delegate (our event) can only have one target. We already have two targets (employee1.OnPayrollArrival and employee2.OnPayrollArrival) - so what do we do? How about if we take our delegate and look at all its targets one by one - and call BeginInvoke on them one at a time? That should work around the original problem.
public class EmployerPublisher
{
public event PayrollArrivedHandler PayrollArrived
// This event firing is synchronous - i.e. the publisher instance is tied down until the subscriber finishes processing OnPayrollArrival
public void FireEvent(DateTime today)
{
if(PayrollArrived != null)
PayrollArrived(today);
}
// This is an asynchronous version of the same event-firing - just fire it - and do not worry about how long the subscriber takes - since subscriber processing is on a different thread.
public void FireEventAsynchronous(DateTime today)
{
if(PayrollArrived != null)
{
Delegate[] delegates = PayrollArrived.GetInvocationList();
foreach(Delegate del in delegates)
{
PayrollArrivedHandler handler = (PayrollArrivedHandler) del;
handler.BeginInvoke(today, null, null)
}
}
}
Gotcha 1 (the badly-behaved subscriber problem) - is essentially addressed by letting the subscribers stay badly behaved. We just don't care - we've still managed to notify all the subscribers successfully.
Gotcha 2 : A subscriber is added while notification is in progress
This is a slightly trickier problem to solve. We still want to allow the new subscriber to add itself - but we do not want to interfere with the current notification process.
This solution is borrowed from Holub on Patterns - and works well in our scenario as well. The basic idea is that instead of having just ANY collection of subscribers - use a linked list to store subscribers.
Notification of the list of subscribers boils down to traversing the linked lists one node at a time. Say you are on some intermediate node - and a new subscriber arrives. Simply add the new subscriber (as a node) to the head of the list!
This way - it doesn't interfere with the existing notification process - and is successfully added to the list of subscribers whenever the next notification comes around.
Gotcha 2 (subscribers added while notification is in progress) - is solved by ensuring that any new subscribers are added to the head of a linked list (containing all the subscribers)
Gotcha 3: Subscribers that fail to unsubscribe
The publisher holds a reference to every subscriber that has subscribed to its events. 2 subscribers – 2 references – 100 subscribers – hundred references. Unless each and every individual subscriber specifically unsubscribes after handling the event, the publisher does not release the reference to that subscriber. This leads to objects hanging around on the managed heap (and also potentially unmanaged resources (files, images etc.) that are held on to by the managed objects).
Gotcha 3 Solution – Ensure that subscribers always unsubscribe from subscribed events when they are done.
NOTE: WPF uses something called the ‘Weak Event Pattern’ (basically a WeakReference from the publisher to its subscribers – which gets garbage collected if it has been idle for a period of time).
Gotcha 4 : Race Condition in the .NET eventing framework
This is not a publish-subscribe problem – but more an ‘eventing’ problem in the .NET framework. This ends up being a publish-subscribe problem nevertheless.
public class EmployerPublisher
{
public event PayrollArrivedHandler PayrollArrived;
public void FireEvent(DateTime today)
{
// There is a possible race condition that can occur here
// If another subscriber - on another thread - unsubscribes from this event,
// the PayrollArrived suddenly becomes null
if(PayrollArrived != null) // WILL THROW A NULLREFERENCE EXCEPTION
PayrollArrived(today);
}
}
Gotcha 4 Solution– A simple workaround is to ensure that the event is always initialized to an empty delegate – this way it will never be null.
public event PayrollArrivedHandler PayrollArrived = delegate {};
Summary
.NET’s eventing framework makes it simple to create a simple Publish Subscribe solution – however – one has to be careful about certain trip hazards. None of these hazards are difficult to deal with – and with just a little due diligence, one can have a powerful messaging subsystem going within any application. Neglecting these little details can, however, cause serious problems including memory leaks, unfulfilled subscribers and even uncaught exceptions.