How to call async methods from within a Prism event aggregator subscriber?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
Prism's EventAggregator uses synchronous Action<T> delegates for subscriptions, which means you cannot directly pass an async method as a subscriber. Calling async void handlers works but swallows exceptions silently and creates fire-and-forget behavior. The proper approach is to subscribe with a synchronous handler that safely invokes your async logic, using patterns like async void with explicit error handling, or bridging to a Task that you track and await elsewhere.
The Problem
Prism's PubSubEvent<T> expects an Action<T> subscriber, not a Func<T, Task>:
The Subscribe method signature is Subscribe(Action<T> action). When you pass an async lambda, the compiler converts it to async void, which means:
- Exceptions thrown inside are unobserved and crash the application
- The caller has no way to know when the async work completes
- No backpressure — events fire faster than handlers can process
Solution 1: Async Void with Explicit Error Handling
The simplest approach — use async void but wrap the entire body in try/catch:
This is the most pragmatic approach for event handlers where you do not need to await the result. The try/catch prevents unobserved exceptions from crashing the app.
Solution 2: Helper Extension Method
Create a reusable extension to subscribe with async handlers safely:
Usage:
Solution 3: UI Thread with Dispatcher
When the async work needs to update the UI (WPF/Xamarin), subscribe on the UI thread:
ThreadOption.UIThread ensures the subscriber runs on the UI thread, and await returns to the UI thread because the SynchronizationContext is captured.
Solution 4: Command-Based Approach
For ViewModels, use Prism's DelegateCommand which has built-in async support:
Thread Safety Considerations
Common Pitfalls
- Unobserved exceptions in async void:
async voidmethods do not propagate exceptions to the caller. An unhandled exception in an async void subscriber crashes the application. Always wrap the body in try/catch. - Deadlocks from
.Resultor.Wait(): Callingtask.Resultortask.Wait()inside a subscriber that runs on the UI thread causes a deadlock because the UI thread is blocked waiting for a task that needs the UI thread to complete. Useawaitinstead. - Forgetting to unsubscribe: Async subscribers hold references that prevent garbage collection. Store the
SubscriptionTokenand callUnsubscribe(token)or usekeepSubscriberReferenceAlive: falseto allow GC cleanup. - Concurrent event handling: Events can fire faster than async handlers complete. Without synchronization (like
SemaphoreSlim), multiple handlers run concurrently on the same shared state, causing race conditions. - Subscribing on the wrong thread:
ThreadOption.PublisherThreadruns the handler on whatever thread published the event. If your handler updates UI elements, useThreadOption.UIThreadto avoid cross-thread access exceptions.
Summary
- Prism's
SubscribetakesAction<T>, so async handlers becomeasync voidimplicitly - Always wrap async void handlers in try/catch to prevent unobserved exceptions
- Create a
SubscribeAsyncextension method for reusable, safe async subscriptions - Use
ThreadOption.UIThreadwhen the handler updates UI-bound properties - Use
SemaphoreSlimto serialize concurrent async event handlers - Store
SubscriptionTokenand unsubscribe when the subscriber is no longer needed

