Raising Events Asynchronously

11 September 2008

I’ve spent a few hours these past couple days researching how to raise events asynchronously, and I would like to share my findings.

Raising an event synchronously in the same thread is pretty simple.

public class FooClass
{
	public event EventHandler FooEvent;

	private void OnFooEvent()
	{
		if (FooEvent != null)
		{
			FooEvent(this, new EventArgs());
		}
	}
}

The above code raises the event and all subscribers to the event are notified and do their respective processing. However, since the event is raised in the same thread, OnFooEvent() cannot proceed until all the subscribers to the event do their processing. This can cause an unwanted delay, specifically if the class raising the event is a Windows form, which would cause the form to freeze and be unresponsive to the user.

Raising events asynchronously lets the event subscribers do their processing in another thread and allows the thread that raised the event to continue processing. And in the case of a form raising an event, the form can still be responsive to user input as the event subscribers do their processing in the background.

Below is a code example on raising an event asynchronously.

public class FooClass
{
	public event EventHandler FooEvent;

	private void OnFooEvent()
	{
		if (FooEvent != null)
		{
			FooEvent.BeginInvoke(this, new EventArgs(), FooEventCallBack, FooEvent);
		}
	}

        /// This method is called when the event subscriber has finished its processing
	private void FooEventCallback(IAsyncResult Result)
	{
		EventHandler raisedEvent = (EventHandler)Result.AsyncState;
		raisedEvent.EndInvoke(Result);
	}
}

You will notice raising an event asynchronously is a bit more involved. First off, the BeginInvoke() method is called (FooEvent.BeginInvoke()) instead of calling the delegate/event itself (FooEvent(this,new EventArgs())). BeginInvoke() raises the event in another thread. The first two arguments are the event arguments. The third argument is the callback method that will be called when the event subscriber has finished its processing. The last argument in BeginInvoke() is the object you want to send to the callback method, and it can be null if so desired. Once BeginInvoke() is called, FooClass continues its processing as the event subscribers are notified in a separate thread.

Now lets take a look at the FooEventCallback() method that is called when the event subscriber has finished its processing. This method takes 1 argument of type IAsyncResult. You must pass in this argument to the EndInvoke() method of the event to free up resources and prevent a memory leak. The AsyncState property on this argument references the last object that was passed into BeginInvoke() (in this case the FooEvent).

One shortcoming of the above code is that it will only work if there is one subscriber to the event. If there are multiple subscribers, then you will receive an ArgumentException error stating “The delegate must have only one target.” To get around this, you must raise the event individually for each target. Take a look at the code below.

public class FooClass
{
	public event EventHandler FooEvent;

	private void OnFooEvent()
	{
		if (FooEvent != null)
		{
			Delegate[] subscribers = FooEvent.GetInvocationList();
			foreach (EventHandler subscriber in subscribers)
			{
				subscriber.BeginInvoke(this, new EventArgs(), FooEventCallBack, subscriber);
			}
		}
	}

        /// This method is called when the event subscriber has finished its processing
	private void FooEventCallback(IAsyncResult Result)
	{
		EventHandler raisedEvent = (EventHandler)Result.AsyncState;
		raisedEvent.EndInvoke(Result);
	}
}

We get the list of subscribers by calling GetInvocationList() on the event. This returns an array of type Delegate. You will notice, however, that within the foreach loop the subscribers are cast to their specific delegate type (foreach (EventHandler subscriber in subscribers)) instead of the base Delegate type (foreach (Delegate subscriber in subscribers)). This has to be done so the event can be raised asynchronously. The base Delegate type does not have a BeginInvoke() method.

Hopefully this helps others with raising event asynchronously. The you download the attachment below to view example source code of raising event asynchronously in action.

EventCallbacks Example Code

 | Posted by admin | Categories: Uncategorized | Tagged: , , |