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:
The output order shows the synchronous behavior clearly:
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:
- A long-running handler can freeze the UI.
- A handler touching UI from a background-raised event can fail unless marshaled correctly.
- 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.
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:
- Event dispatch starts synchronously.
- An
async voidhandler may continue later. - 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:
- Queuing work with
Task.Runinside handlers when appropriate. - Publishing messages onto a channel or queue.
- 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 voidhandlers 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.

