Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

EventBroker: a notification component for synchronous and asynchronous, loosly coupled event handling

4.89/5 (25 votes)
26 Oct 2008Apache9 min read 1   2K  
EventBroker is a notification component for (a)synchronous loosly coupled event handling.

EventBroker.png

Preface

This article is a response to the article Weak Events in C# by Daniel Grunwald.

Introduction

EventBroker is a component that you can use to fire and receive notifications in your system.

Features

  • Loose Coupling:
  • The subscriber does not have to know the publisher. Both of them only need to know the EventTopic URI (a string uniquely identifying the EventTopic in the system). This facilitates building loosely coupled systems.

  • Thread Synchronization:
  • The subscriber defines on which thread the subscription handler is executed:

    • Same thread as publisher (synchronous)
    • Background thread (asynchronous)
    • User interface thread (synchronous or asynchronous)

    Publishers can restrict subscriptions to events to be synchronous or asynchronous.

  • Multiple Publishers/Subscribers:
  • Several publishers/subscribers can fire/handle the same EventTopic.

  • Weak References:
  • Publishers and subscribers are referenced by weak references which will not prevent them from being garbage collected – like in the normal event registration model.

  • Scope:
    • Scope per EventBroker:
    • Only publishers and subscribers that are registered on the same EventBroker are wired together when events are fired. Normally, you'll use a single EventBroker in your system that handles all event notifications. However, in special cases, it is useful to define a new EventBroker for a sub system. This gives you the possibility to define a scope for your event notification.

    • Scope with hierarchical naming:
    • Publishers and subscribers can be named in a hierarchical way, and events can be global, to parents only, or to children only. Both publishers and subscribers can define the scope they publish to/receive from.

Background

This EventBroker is based upon the EventBroker from the Composite (UI) Application Block from Microsoft. See the section Comparison to CAB EventBroker below, for differences.

Using the code

Sample publisher

Publish an event topic:

C#
public class Publisher
{
    [EventPublication("topic://EventBrokerSample/SimpleEvent")]
    public event EventHandler SimpleEvent;
    
    ///<summary>Fires the SimpleEvent</summary>
    public void CallSimpleEvent()
    {
        SimpleEvent(this, EventArgs.Empty);
    }
}

Register the publisher with your event broker (you have to hold an instance of the event broker somewhere in your code). The sample assumes there is a service that holds the event broker instance for us:

C#
EventBroker eb = Service.EventBroker;
Publisher p = new Publisher();
eb.Register(p);

On registration of the publisher, the event broker inspects the publisher for published events (events with the EventPublication attribute).

Sample subscriber

Subscribe to an event topic:

C#
public class Subscriber
{
    [EventSubscription(
        "topic://EventBrokerSample/SimpleEvent", 
        typeof(Handlers.Publisher))]
    public void SimpleEvent(object sender, EventArgs e)
    {
        // do something useful or at least funny
    }
}

Register the subscriber with the event broker:

C#
EventBroker eb = Service.EventBroker; 
Subscriber s = new Subscriber();
eb.Register(s); 

The event broker will inspect the subscriber, on registration, for subscription to event topics (methods with the EventSubscription attribute).

If a publisher fires an event topic for that subscribers are registered, then the event broker will relay them to the subscribers by calling the subscription handler methods with the sender and Eventargs the publisher used to fire its event.

Publication options

Simple

C#
[EventPublication("Simple")]
public event EventHandler SimpleEvent;

With custom Eventargs

Note: CustomEventArgs has simply to be derived from EventArgs.

C#
[EventPublication("CustomEventArgs")]
public event EventHandler<CustomEventArguments> CustomEventArgs;

Publish multiple event topics with one single event

C#
[EventPublication("Event1")]
[EventPublication("Event2")]
[EventPublication("Event3")]
public event EventHandler MultiplePublicationTopics;

Allow only synchronous subscription handlers

See the sections Comparison to CAB EventBroker/Subscription handler restrictions for more details.

C#
[EventPublication("test", HandlerRestriction.Synchronous)]
public event EventHandler AnEvent; 

Allow only asynchronous subscription handlers

See the section Comparison to CAB EventBroker/Subscription handler restrictions for more details.

C#
[EventPublication("test", HandlerRestriction.Asynchronous)]
public event EventHandler AnEvent;

Subscription options

Simple

C#
[EventSubscription("Simple", typeof(Handlers.Publisher)]
public void SimpleEvent(object sender, EventArgs e) {} 

Custom Eventargs

C#
[EventSubscription("CustomEventArgs"), typeof(Handlers.Publisher))]
public void CustomEventArgs(object sender, CustomEventArgs e) {} 

Subscribe multiple event topics

C#
[EventSubscription("Event1", typeof(Handlers.Publisher))]
[EventSubscription("Event2", typeof(Handlers.Publisher))]
[EventSubscription("Event3", typeof(Handlers.Publisher))]
public void MultipleSubscriptionTopics(object sender, EventArgs e) {} 

Execute handler on background thread (asynchronous)

The event broker creates a worker thread to execute the handler method on. The publisher can immediately continue processing.

C#
[EventSubscription("Background", typeof(Handlers.Background))]
public void BackgroundThread(object sender, EventArgs e) {} 

Execute handler on UI thread

Use this option if calling from a background worker thread to a user interface component that updates the user interface - no need for Control.Invoke(...) anymore.

C#
[EventSubscription("UI", typeof(Handlers.UserInterface))]
public void UI(object sender, EventArgs e) {}

Note that if you use the UserInterface handler, then you have to make sure that you register the subscriber on the user interface thread. Otherwise, the EventBroker won't be able to switch to the user interface thread, and will throw an exception.

Execute handler on UI thread asynchronously

The same as above, but the publisher is not blocked until the subscriber has processed the event.

C#
[EventSubscription("UIAsync", typeof(Handlers.UserInterfaceAsync))]
public void UI(object sender, EventArgs e) {}  

Fire event topics directly on EventBroker

Event topics can be fired directly on the EventBroker without the need of registering a publisher. This comes in handy whenever you need to fire an event topic from an object that lives only very shortly.

A good example for such a scenario is a scheduled job execution. At the scheduled time, an instance of a job class is instantiated and executed. It would be cumbersome to register this instance, fire the event, and unregister it again - it's far easier to fire the event directly on the EventBroker:

C#
eventBroker.Fire("topic", sender, eventArgs);

Scope

Sometimes it is necessary to limit the scope an event is published to. This can be achieved in two different ways:

Multiple instances of event brokers

Subscribers can only listen to events of publishers that are registered on the same event broker. Therefore, an event broker automatically builds a scope.

This is the easiest solution to have several event handling scopes in your application, and should always be preferred. However, sometimes, you need more control over scopes within the objects on one single event broker. This scenario is described in the following section.

Hierarchical naming

Publishers and subscribers can be named by implementing the INamedItem interface. This interface provides a single property:

C#
string EventBrokerItemName { get; }

This allows identifying an object within the event broker whereas the publication and subscription attributes are always bound to the class.

The naming is hierarchical by using the same scheme as namespaces:

  • Test is a parent of Test.MyPublisher1
  • Test.MyName1 is a sibling of Test.MyName2
  • Test.MyName1.Subname is a child of Test.MyName1
  • Test.MyName1 is a twin of Test.MyName1 (two objects with the same name are twins)

Now, a publisher can define the scope it wants to publish the event to, by defining a scope in the publication attribute:

C#
[EventPublication("Topic")]
public event EventHandler PublishedGlobally;

[EventPublication("Topic", typeof(ScopeMatchers.PublishToParents)]
public event EventHandler PublishedToParentsAndTwinsOnly;

[EventPublication("Topic", typeof(ScopeMatchers.PublishToChildren)]
public event EventHandler PublishedToChildrenAndTwinsOnly;

The first event is a global event that all subscribers can receive. The second event is only passed to subscribers that are parents or twins of the publisher. The third event is only passed to subscribers that are children or twins of the publisher.

A subscriber can define the scope it wants to receive events from accordingly:

C#
[EventSubscription("Topic", typeof(Handlers.Publisher)]
public void GlobalHandler(object sender, EventArgs e)

[EventSubscription(
    "Topic", 
    typeof(Handlers.Publisher), 
    typeof(ScopeMatchers.SubscribeToParents)]
public void ParentHandler(object sender, EventArgs e)

[EventSubscription(
    "Topic", 
    typeof(Handlers.Publisher), 
    typeof(ScopeMatchers.SubscribeToChildren)]
public void ChildrenHandler(object sender, EventArgs e)

The first subscription is a global subscription that will handle all the events passed to it. The second subscription will only be called when a parent of the subscriber is firing the event. The third subscription will only be called when a child of the subscriber is firing the event.

Twins (different objects with the same name) are handled specially. A twin is always a parent and child of its twin, and will therefore will always receive all the events from its twin.

Comparison to CAB EventBroker

As stated in the Background section, my EventBroker is based on the EventBroker from CAB (Composite UI Application Block) from the Practices and Patterns group of Microsoft.

This section describes the differences.

Standalone

The bbv.Common EventBroker can be used standalone. You can use it anywhere in your projects you need notification, without any framework constraints as CAB would require.

Developer guidance

I tried to implement the EventBroker in a way that errors get visible as soon as possible. I will give you some examples of what that means:

  • If the type of EventHandler the published event provides does not match the signature the subscription handler provides, then an exception is thrown at registration time, instead of when the event is fired.
  • If you use the UserInterface or UserInterfaceAsync subscription handlers, then an exception is thrown at registration time if no WindowsFormsSynchronizationContext is present, that means the current thread is not the user interface thread. This would lead to cross threading exceptions when handling events.

Subscription handler restrictions

Note: this feature is only available in the version hosted on Sourceforge.net (see the section Download) and not in the attached solution (it's too new ;-) ).

We had the problem that some publisher has to be sure that all subscribers handle its event synchronously. For example, if you publish a cancel event, providing a way for all subscribers to cancel the current operation. Only if no subscriber sets the Cancel property on the CancelEventArgs to true can the publisher proceed with its operation. Therefore, the publisher has to restrict all subscriptions on this event to be synchronous:

C#
[EventPublication("test", HandlerRestriction.Synchronous)]
public event EventHandler AnEvent;

If a subscriber registers an asynchronous handler for this event, then an exception is thrown. Note that the exception is thrown at registration time, and not when the event is fired. This simplifies writing consistent code a lot.

Furthermore, a publisher can restrict subscription handlers to be asynchronous because the publisher does not want to be blocked:

C#
[EventPublication("test", HandlerRestriction.Asynchronous)]
public event EventHandler AnEvent;

Logging

The EventBroker of bbv.Common is quite chatty in terms of log messages. This enables you to see when a publisher fires an event, how they are routed to subscribers, how they are handled, and which exceptions occurred.

Note: bbv.Common uses log4net to log messages. That means, you can configure the level of messages logged for each component.

Extensibility

ThreadOption --> Handler

I replaced the enum ThreadOption with extensible handlers to define the way the event is handled (synchronously, asynchronously).

Scope --> Scope Matcher

You can implement your own scope matchers to provide an event hierarchy that suits your needs instead of specifying a scope with an enum.

Internals

We'll have a look at the internals on a next update of this article. Until then, please refer to the source code available in the download.

Download

The download at the top of this article contains three Visual Studio 2008 projects:

  1. bbv.Common.EventBroker: the event broker component.
  2. bbv.Common.EventBroker.Test: the unit tests (NUnit).
  3. bbv.Common.EventBroker.Sample: a small sample application (basic usage of event broker).

The bbv.Common.EventBroker is part of a larger library containing some other cool components, which can be found here.

This download is just a stripped down version. To try it out, open the solution, set the start up project to bbv.Common.EventBroker.Sample, and hit F5.

Note that the version attached to this article is not the latest available. Be sure to check the Sourceforge page for the latest release.

License

Note that the bbv.Common.EventBroker is licensed under Apache License 2.0, but because the EventBroker contains parts of the CAB from Microsoft, additional license restrictions have to be applied, see file headers in the source code for details.

History

  • 2008-10-11 - Initial version.
  • 2008-10-19 - Added comparison to CAB EventBroker, added link to Sourceforge.net.
  • 2008-10-26 - Added the section Fire event topics directly on EventBroker.

License

This article, along with any associated source code and files, is licensed under The Apache License, Version 2.0