Handling Multiple Event Exceptions in C
In C#, an event handler exception occurs when a subscriber's method throws an unhandled error during event invocation, which typically halts the invocation pipeline and prevents subsequent subscribers from executing. This article explains the mechanics behind event handler exceptions, the limitations of standard invocation patterns, and how to use the invocation list to ensure all subscribers run while aggregating any thrown exceptions for clean, predictable error handling.
Understanding Event Handler Exceptions
In C#, events rely on multicast delegates. When an event is raised
using the standard invocation pattern—such as
MyEvent?.Invoke(this, EventArgs.Empty)—the runtime executes
each subscriber's method sequentially on the calling thread.
If any subscriber throws an unhandled exception, the execution chain breaks immediately. The calling code receives that single exception, but any subscribers remaining in the invocation queue are skipped entirely. This behavior can leave your application in an inconsistent state, as some parts of your system never receive critical state updates.
The Problem with Default Invocation
Consider an event with three subscribers: Subscriber A, Subscriber B, and Subscriber C.
public event EventHandler ProcessCompleted;
public void CompleteProcess()
{
// If Subscriber A throws an exception, Subscribers B and C will never execute.
ProcessCompleted?.Invoke(this, EventArgs.Empty);
}If Subscriber A throws an exception, the invocation loop terminates. Subscriber B and Subscriber C never execute, and you lose visibility into whether B or C would have succeeded or thrown additional errors.
Handling Multiple Throwing Subscribers
To ensure that every subscriber executes regardless of failures
elsewhere in the chain, you must manually iterate through the delegate's
invocation list and isolate each call in a try-catch block.
You can collect any errors that occur and wrap them in an
AggregateException.
Implementation Pattern
Use the Delegate.GetInvocationList() method to obtain an
array of individual delegates attached to the event.
using System;
using System.Collections.Generic;
public class EventPublisher
{
public event EventHandler<EventArgs> DataProcessed;
public void OnDataProcessed()
{
var handler = DataProcessed;
if (handler == null)
{
return;
}
var exceptions = new List<Exception>();
// Get each individual subscriber delegate
foreach (Delegate singleCast in handler.GetInvocationList())
{
try
{
// Explicitly invoke each subscriber individually
singleCast.DynamicInvoke(this, EventArgs.Empty);
}
catch (Exception ex)
{
// DynamicInvoke wraps exceptions in a TargetInvocationException;
// capture InnerException to preserve the original error.
exceptions.Add(ex.InnerException ?? ex);
}
}
// If any subscribers threw exceptions, throw an AggregateException
if (exceptions.Count > 0)
{
throw new AggregateException("One or more event subscribers encountered an error.", exceptions);
}
}
}Strongly Typed Invocation Alternative
Using DynamicInvoke introduces slight reflection
overhead. If performance is critical, cast each item in the invocation
list to the specific delegate type:
foreach (EventHandler<EventArgs> singleCast in handler.GetInvocationList())
{
try
{
singleCast(this, EventArgs.Empty);
}
catch (Exception ex)
{
exceptions.Add(ex);
}
}Directly invoking the cast delegate removes the need to unwrap a
TargetInvocationException, as the original exception is
caught directly.
Handling Asynchronous Subscribers
If your event uses an asynchronous pattern (such as returning
Task), iterate through the subscribers, invoke them,
collect their returned Task objects, and await them
collectively using Task.WhenAll. This ensures all
asynchronous tasks run to completion while preserving all thrown
exceptions inside the resulting AggregateException.