This post does three things:

  1. Provides a complete implementation of the publish subscribe pattern in C#.
  2. Highlights 4 potential issues that are inherent to ANY publish subscribe implementation – including the one provided here.
  3. Provides original, tested workarounds for each of the 4 issues (source code included).

As far as the author is aware, this is the only publish subscribe implementation that provides these workarounds – making it immune from the common issues of badly behaved subscribers, race conditions and subscribers that fail to unsubscribe. 

UPDATE: With the introduction of two new interfaces (IObserver and IObservable) in .NET 4.0, there is a cleaner way to implement the Publish Subscribe pattern in C# .NET. However, the ‘gotchas’ listed here still apply. Even with the new interfaces, one still needs to worry about things such as ‘unsubscribing from the publisher’, ‘badly behaved subscribers’ and ‘new subscribers added while publishing is in progress’.

DELEGATES and EVENTS way OF IMPLEMENTING PUBLISH SUBSCRIBE in C#

 

Introduction : Events , combined with delegates in C#  make it possible to implement a simple publisher – subscriber pattern. If you are unfamiliar with delegates, read this post first.

There are three main players in any Publish Subscribe pattern implementation:

  1. The Event : The example below revolves around the ‘payday’ event – which is called PayrollArrived.
  2. The Publisher : The event is published by the Employer class (the Employer announces the payday when it arrives).
  3. The Subscriber : Each Employee (i.e. each instance of the Employee class) listens for the event – and is hence a subscriber to the PayrollArrived event.

 

The Event

Events provide a way to signal a change of state to whoever is ‘subscribed’ to that event. The arrival of the first of every month may be the event for publishing a new issue of a magazine. The subscribers would be everyone who has a paid subscription to the magazine – and the publisher would be the magazine publisher.

The arrival of the 15th of every month may be a payroll event for a company. The subscribers would be all employees who get paid on the payroll date.  The publisher would be the company itself that pays the employees on payroll day. The example code in this post deals with the latter event – a payroll event with an Employer (Publisher) and multiple Employees(Subscribers). 

// For events to be treated as 'fields', they need to be of type 'delegate'.

// First, define the delegate type for the event 

   public delegate void PayrollArrivedHandler(object sender, PublisherEventArgs e);

 

// public event declaration, a 'delegate' type for the event must be declared first

   public event PayrollArrivedHandler PayrollArrived;

The Publisher (Employer)

public class EmployerPublisher 

{

    LinkedList<Employee> _listSubscribers = new LinkedList<Employee>();

    public LinkedList<Employee> ListSubscribers

    {

        get { return _listSubscribers; }

        set { _listSubscribers = value; }

    }

 

    // For events to be treated as 'fields', they need to be of type 'delegate'.

    // First, define the delegate type for the event 

    public delegate void PayrollArrivedHandler(object sender, PublisherEventArgs e);

 

    // public event declaration, a 'delegate' type for the event must be declared first

    public event PayrollArrivedHandler PayrollArrived;

  

    // add new subscriber

    public void AddSubscriber(Employee em)

    {

        // Add new subscriber to the TOP of the linked list - this way it will not interfere with the notification in progress

        this.ListSubscribers.AddFirst(em);

 

        // Subscribe the new subscriber to the event.

        // The coupling between the publisher publishing and the subscriber subscribing is via the common delegate

        this.PayrollArrived += em.OnPayrollArrival;

    }

 

    // This event firing is synchronous - i.e. the publisher instance is tied down until the subscriber finishes processing OnPayrollArrival

    public void FireEvent(DateTime dayToFireEvent)

    {

        PublisherEventArgs args = new PublisherEventArgs();

        args.DateToFireEvent = dayToFireEvent;

 

        if (PayrollArrived != null)

            PayrollArrived(this, args);

    }

 

    // This is an asynchronous version of the same event-firing - just fire it - 

    // and do not CARE about how long the subscriber takes - since subscriber processing is on a different thread.

    // This works around the 'Badly behaved subscriber - Gotcha 1'.

    public void FireEventAsynchronous(DateTime dayToFireEvent)

    {

        if (PayrollArrived != null)

        {

            PublisherEventArgs args = new PublisherEventArgs();

            // We have more than one subscriber - employee 1 and empoyee 2. Each one subscribes via its own delegate. 

            // so - we need to loop over all the delegates

 

            Delegate[] delegates = PayrollArrived.GetInvocationList();

            foreach (Delegate del in delegates)

            {

                PayrollArrivedHandler handler = (PayrollArrivedHandler) del;

                handler.BeginInvoke(this, args, null, null);

            }

        }

    }

 

    public void RemoveSubscriber(Employee em)

    {

        this.PayrollArrived -= em.OnPayrollArrival;

    }

}

The Subscriber (Employee)

// Subscriber class – Employee – interested in knowing when the PayrollArrived event occurs

public class Employee 

{

   private string _name;

 

   public string Name

   {

       get { return _name; }

       set { _name = value; }

   }

 

   public Employee(string name)

   {

       this.Name = name;

   }

 

   public void OnPayrollArrival(object sender, PublisherEventArgs args)

   {

       DateTime dateToday = args.DateToFireEvent.Date;

 

       // Round up some friends and go spend the money!

       Console.WriteLine(this.Name + " was notified. Today is payday - date :" + dateToday);

   }

}

Most implementations out there have just the code above. They do not account for various things that can go wrong with a simple publish subscribe implementation.

 

What can go wrong? (The 4 main potential issues with any publish-subscribe implementation)

 

Gotcha 1 – The badly behaved subscriber

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 in its event handler. How do we prevent such an occurrence?

Gotcha 2 – New Subscriber while notification is in progress

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 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.

Gotcha 4 – Race conditions in the .NET framework

Race conditions in the .NET eventing framework – leading to inconsistent handling of events.

Workarounds for each of the Gotchas

 

Gotcha 1 Workaround 

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. 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 – see sample below).

// This is an asynchronous version of the same event-firing - just fire it - 

// and do not CARE about how long the subscriber takes - since subscriber processing is on a different thread.

// This works around the 'Badly behaved subscriber - Gotcha 1'.

public void FireEventAsynchronous(DateTime dayToFireEvent)

{

    if (PayrollArrived != null)

    {

        PublisherEventArgs args = new PublisherEventArgs();

        // We have more than one subscriber - employee 1 and empoyee 2. Each one subscribes via its own delegate. 

        // so - we need to loop over all the delegates

        Delegate[] delegates = PayrollArrived.GetInvocationList();

        foreach (Delegate del in delegates)

        {

            PayrollArrivedHandler handler = (PayrollArrivedHandler) del;

            handler.BeginInvoke(this, args, 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? Just loop over all the targets of our delegate – and call BeginInvoke on them one at a time. That should work around the original problem.

Gotcha 1 (Badly behaved subscribers) Workaround Summary

The badly-behaved subscriber problem is essentially addressed by launching each subscriber on a separate thread (and 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) Workaround

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.

 

LinkedList<Employee> _listSubscribers = new LinkedList<Employee>();

public LinkedList<Employee> ListSubscribers

{

    get { return _listSubscribers; }

    set { _listSubscribers = value; }

}

// add new subscriber

   public void AddSubscriber(Employee em)

   {

       // Add new subscriber to the TOP of the linked list - this way it will not interfere with the notification in progress

       this.ListSubscribers.AddFirst(em);

Gotcha 2  Workaround Summary

Adding new subscribers 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 – 100 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 for longer than they are needed.

To work around this, simply ensure that a RemoveSubscriber method is available that can be called ON each subscriber (by the publisher) when it is done with the subscriber.

public void RemoveSubscriber(Employee em)

{

     this.PayrollArrived -= em.OnPayrollArrival;

}

Gotcha 3 Workaround Summary

Ensure that subscribers always unsubscribe from subscribed events when they are done. A simple RemoveSubscriber method as shown above can help accomplish this.

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.

// initialize to empty delegate to avoid Gotcha 4 - race condition      

 public event PayrollArrivedHandler PayrollArrived = delegate {} ;

Gotcha 4 Workaround Summary

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

The publish subscribe pattern is one of the most ubiquitous patterns in use today. Events and delegates in c# make it relatively simple to implement the pattern with a few lines of code. However, this implementation is prone to performance issues and potential bugs – if certain conditions are not handled up front.

With the 4 workarounds described in this post, the publish subscribe implementation (in C#) is as close to unbreakable as possible. Subscribers can continue to be long running , poorly behaved. Race conditions can try and cause unanticipated interjections. The simple workarounds in the sample code will handle these conditions.

These problems are especially noticeable when your event has a LARGE number of subscribers.

Full Source Code

Download Full Solution with the workarounds 

References

Anuj holds professional certifications in Google Cloud, AWS as well as certifications in Docker and App Performance Tools such as New Relic. He specializes in Cloud Security, Data Encryption and Container Technologies.

Initial Consultation

Anuj Varma – who has written posts on Anuj Varma, Hands-On Technology Architect, Clean Air Activist.