Abstract
This is a tutorial article on Asynchronous Events invocation in C#. We discuss treading issues related to the usage of Events/EventHandlers in C#. The intended audience is Intermediate C# programmers and above.
Introduction
Modern language like C# has integrated the Event mechanism, which has practically integrated the Observer pattern into language mechanisms.
The fact that the Event mechanism, in reality, provides Synchronous calls is often overlooked and not enough emphasized. Programmers often have the illusion of parallelism, which is not reality and is an important issue in today’s multi-core-processors world. We next provide analysis and solutions to multithreading problems.
The code presented is tutorial, demo-of-concept level and for brevity does not handle or show all variants/problematic issues.
The event mechanism provides Synchronously calls on a single thread
What needs to be emphasized is that in a call.
if (SubjectEvent != null)
{
SubjectEvent(this, args);
}
//or
SubjectEvent?.Invoke(this, args);
subscribed EventHandlers are being invoked Synchronously on a single thread. That has some not-so-obvious consequences.
- EventHandlers are executed in sequence, one after another, in the order they are subscribed to the event.
- That means that objects/values in earlier subscribed EventHandler are updated earlier than in other EventHandlers, which might have consequences for program logic
- Call to a certain EventHandler blocks the thread until all work in that EventHandler is completed
- If an Exception is thrown in a certain EventHandler, all EventHandlers subscribed after that one will not be executed
We will demo that in an example. The plan is to create 3 EventHandlers, each taking 10 seconds to finish, and to monitor threads on which each EventHandler is running, and the total time taken. We will output each ThreadId that is relevant for this example to see how many threads are being used.
public class EventArgsW : EventArgs
{
public string StateW = null;
}
public class EventWrapper
{
public event EventHandler<EventArgsW> EventW;
public string StateW;
public void Notify()
{
Console.WriteLine("Notify is running on ThreadId:{0}",
Thread.CurrentThread.ManagedThreadId);
EventArgsW args = new EventArgsW();
args.StateW = this.StateW;
EventW?.Invoke(this, args);
}
}
public class HandlerWrapper
{
private string name;
private string StateW;
private ManualResetEvent mrs;
public HandlerWrapper(string name, ManualResetEvent mrs)
{
this.name = name;
this.mrs = mrs;
}
public void Handler(object subject, EventArgsW args)
{
Console.WriteLine("Handler{0} is running on ThreadId:{1}",
name, Thread.CurrentThread.ManagedThreadId);
Worker(subject, args);
}
private void Worker(object subject, EventArgsW args)
{
Console.WriteLine("Handler{0}.Worker is running on ThreadId:{1}, i:0",
name, Thread.CurrentThread.ManagedThreadId);
StateW = args.StateW;
for (int i = 1; i <= 2; ++i)
{
Thread.Sleep(5000);
Console.WriteLine("Handler{0}.Worker is running on ThreadId:{1}, i:{2}",
name, Thread.CurrentThread.ManagedThreadId, i);
}
mrs.Set();
}
}
internal class Client
{
public static void Main(string[] args)
{
Console.WriteLine("Client is running on ThreadId:{0}",
Thread.CurrentThread.ManagedThreadId);
ManualResetEvent[] mres = new ManualResetEvent[3];
for (int i = 0; i < mres.Length; i++) mres[i] = new ManualResetEvent(false);
EventWrapper s = new EventWrapper();
s.EventW += (new HandlerWrapper("1", mres[0])).Handler;
s.EventW += (new HandlerWrapper("2", mres[1])).Handler;
s.EventW += (new HandlerWrapper("3", mres[2])).Handler;
// Change subject state and notify observers
s.StateW = "ABC123";
var timer = new Stopwatch();
timer.Start();
s.Notify();
ManualResetEvent.WaitAll(mres);
timer.Stop();
TimeSpan timeTaken = timer.Elapsed;
string tmp1 = "Client time taken: " + timeTaken.ToString(@"m\:ss\.fff");
Console.WriteLine(tmp1);
Console.ReadLine();
}
}
The execution result is

As can be seen from the execution result, EventHandlers run one after another, all on thread Id=1, the same thread as the Client is running on. It took 30.059 seconds to finish all work.
Asynchronous Events using TPL
Using Task Parallel Library (TPL) we can make our EventHandlers run asynchronously on separate threads. Even more, if we want to free the Client thread from any work (let’s say our Client is UI thread), we can raise Event (dispatch EvanHandlers invocations) on a separate thread from the Client thread. Here is the new implementation.
The new solution code is here.
public class EventArgsW : EventArgs
{
public string StateW = null;
}
public class EventWrapper
{
public event EventHandler<EventArgsW> EventW;
public string StateW;
public void Notify()
{
Task.Factory.StartNew(
() => {
Console.WriteLine("Notify is running on ThreadId:{0}",
Thread.CurrentThread.ManagedThreadId);
EventArgsW args = new EventArgsW();
args.StateW = this.StateW;
EventW?.Invoke(this, args);
});
}
}
public class HandlerWrapper
{
private string name;
private string StateW;
private ManualResetEvent mrs;
public HandlerWrapper(string name, ManualResetEvent mrs)
{
this.name = name;
this.mrs = mrs;
}
public void Handler(object subject, EventArgsW args)
{
Console.WriteLine("Handler{0} is running on ThreadId:{1}",
name, Thread.CurrentThread.ManagedThreadId);
Task.Factory.StartNew(
() => Worker(subject, args)); ;
}
private void Worker(object subject, EventArgsW args)
{
Console.WriteLine("Handler{0}.Worker is running on ThreadId:{1}, i:0",
name, Thread.CurrentThread.ManagedThreadId);
StateW = args.StateW;
for (int i = 1; i <= 2; ++i)
{
Thread.Sleep(5000);
Console.WriteLine("Handler{0}.Worker is running on ThreadId:{1}, i:{2}",
name, Thread.CurrentThread.ManagedThreadId, i);
}
mrs.Set();
}
}
internal class Client
{
public static void Main(string[] args)
{
Console.WriteLine("Client is running on ThreadId:{0}",
Thread.CurrentThread.ManagedThreadId);
ManualResetEvent[] mres = new ManualResetEvent[3];
for (int i = 0; i < mres.Length; i++) mres[i] = new ManualResetEvent(false);
EventWrapper s = new EventWrapper();
s.EventW += (new HandlerWrapper("1", mres[0])).Handler;
s.EventW += (new HandlerWrapper("2", mres[1])).Handler;
s.EventW += (new HandlerWrapper("3", mres[2])).Handler;
// Change subject state and notify observers
s.StateW = "ABC123";
var timer = new Stopwatch();
timer.Start();
s.Notify();
ManualResetEvent.WaitAll(mres);
timer.Stop();
TimeSpan timeTaken = timer.Elapsed;
string tmp1 = "Client time taken: " + timeTaken.ToString(@"m\:ss\.fff");
Console.WriteLine(tmp1);
Console.ReadLine();
}
}
The execution result is here

As it can be seen from the execution result, we see EventHandlers running on separate threads, concurrency can be seen from the execution log, and the total time taken is 10.020 seconds.
Asynchronous Events using TPL – Extension method
Since the usage of TPL required changing existing code and obfuscating the readability of code, I created an Extension method to simplify the usage of TPL. Instead of writing.
EventW?.Invoke(this, args);




Join the conversation! Your thoughts help the community grow.