Publish Subscribe Pattern in C# and some gotchas
The eventing mechanism built into the .NET runtime makes it a piece of cake to implement a simple publisher – subscriber pattern (C# code follows).
public class EmployerPublisher { public event PayrollArrivedHandler PayrollArrived; public void FireEvent(DateTime today) { if(PayrollArrived != null) PayrollArrived(today); } } // Subscriber class - Employee public class Employee { public void OnPayrollArrival(DateTime day) { // Round up from friends and go spend the money! } } // Hooking up the publisher and subscriber(s) in the client code EmpoyerPublisher 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 couple of gotchas to watch out for:
- When multiple subscribers are present – how do you ensure that the publisher does not get blocked – i.e. – will each subscriber be well-behaved enough to only perform short tasks?
- What if a subscriber is added while notification is in progress – i.e. – a 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 – i.e. is badly behaved.
Let us look at these one by one:
Problem 1 – The badly-behaved (subscriber) problem
The well-behaved 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?
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.
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 OnPayrollArrivalpublic 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); } }
Problem 1 (the badly-behaved subscriber problem) is essentially taken care of - by letting the subscribers stay badly behaved - we just don't care - we've still managed to notify all the subscribers successfully.
Problem 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!
The head of the list is not affected by where the traversal’s current pointer is. 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.
Leave a Reply