C# events
synchronous programming
.NET
event handling
programming concepts

Are C events synchronous?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Yes, C# events are synchronous by default. When code raises an event, each subscribed handler is invoked on the same thread, one after another, and the caller does not continue until those handlers return. That default behavior is simple and predictable, but it also means slow or failing subscribers directly affect the publisher.

What "Synchronous" Means for Events

An event in C# is built on a delegate invocation list. Raising the event is essentially a method call through that delegate chain. If three handlers are subscribed, the publisher calls all three in sequence before moving on.

A minimal example:

csharp
1using System;
2
3public class Counter
4{
5    public event EventHandler? Changed;
6
7    public void Increment()
8    {
9        Console.WriteLine("Before event");
10        Changed?.Invoke(this, EventArgs.Empty);
11        Console.WriteLine("After event");
12    }
13}
14
15public static class Program
16{
17    public static void Main()
18    {
19        var counter = new Counter();
20
21        counter.Changed += (sender, args) =>
22        {
23            Console.WriteLine("Handler 1");
24        };
25
26        counter.Changed += (sender, args) =>
27        {
28            Console.WriteLine("Handler 2");
29        };
30
31        counter.Increment();
32    }
33}

The output order shows the synchronous behavior clearly:

text
1Before event
2Handler 1
3Handler 2
4After event

The publisher does not continue past Invoke until both handlers finish.

Handlers Run on the Raising Thread

Events do not automatically switch threads. If a UI thread raises the event, the handlers run on that UI thread. If a background worker raises it, the handlers run there instead.

That matters because it affects responsiveness and thread affinity:

  1. A long-running handler can freeze the UI.
  2. A handler touching UI from a background-raised event can fail unless marshaled correctly.
  3. The publisher and subscriber are more tightly coupled in timing than many developers expect.

In other words, an event is not a message queue. It is a synchronous callback mechanism unless you make it something else.

Exceptions Also Propagate Synchronously

Another consequence of synchronous invocation is that exceptions thrown by a handler bubble back through the event raise.

csharp
1using System;
2
3public class Publisher
4{
5    public event EventHandler? Happened;
6
7    public void Raise()
8    {
9        Happened?.Invoke(this, EventArgs.Empty);
10    }
11}
12
13public static class Program
14{
15    public static void Main()
16    {
17        var publisher = new Publisher();
18        publisher.Happened += (s, e) => throw new InvalidOperationException("boom");
19
20        try
21        {
22            publisher.Raise();
23        }
24        catch (Exception ex)
25        {
26            Console.WriteLine(ex.Message);
27        }
28    }
29}

If one handler throws, later handlers may never run unless the publisher or subscribers deliberately isolate failures.

async Event Handlers Change the Shape, Not the Default

Developers sometimes attach async handlers and assume that makes the event itself asynchronous. It does not. An async void event handler returns immediately after it reaches an await, but the publisher still invoked it synchronously and has no task to await.

That creates a subtle model:

  1. Event dispatch starts synchronously.
  2. An async void handler may continue later.
  3. The publisher cannot observe completion reliably.

This is why async void event handlers should be used carefully. They are sometimes necessary in UI frameworks, but they make error propagation and sequencing harder to reason about.

Make Events Asynchronous Only on Purpose

If you truly need asynchronous fan-out, build it intentionally rather than assuming normal events already provide it. Options include:

  1. Queuing work with Task.Run inside handlers when appropriate.
  2. Publishing messages onto a channel or queue.
  3. Designing a custom async callback model that returns Task.

Those designs have tradeoffs, but they make the asynchrony explicit. Ordinary C# events do not.

Design with the Coupling in Mind

Synchronous events are often exactly what you want for in-process notifications because they are simple, ordered, and easy to trace. They become problematic when handlers are expensive, unpredictable, or owned by distant parts of the system. In those cases, the publisher is paying the latency cost of subscribers, which is often a sign that a queued or asynchronous mechanism would be a better fit.

Common Pitfalls

  • Assuming an event behaves like a background message bus when it actually runs handlers synchronously.
  • Raising events on the UI thread and then doing expensive work in subscribers.
  • Forgetting that exceptions from subscribers propagate back to the publisher.
  • Treating async void handlers as if the publisher can await their completion.
  • Using events for loosely coupled workflows that really need a queue or explicit async contract.

Summary

  • C# events are synchronous by default.
  • Handlers run sequentially on the same thread that raises the event.
  • The publisher waits for handlers to return before continuing.
  • Exceptions from handlers propagate through the event raise unless isolated deliberately.
  • If you need true asynchronous behavior, design it explicitly instead of assuming events provide it.

Course illustration
Course illustration

All Rights Reserved.