ZeroToSky

Why Your async void Handler Is Swallowing Exceptions

An async void method cannot be awaited, so any exception it throws escapes to the synchronization context and takes the process down. Here is what actually happens and what to write instead.

Gaurav 2 min read Sign in to save
On this page

Most C# developers learn early that async void is bad, and most stop there. The reason it's bad is more specific and more interesting than "avoid it." pickupnotdone

What actually happens

When you mark a method async Task, the compiler hands you a Task that captures any exception thrown inside. Awaiting that task rethrows it at the call site, where your try/catch can see it.

async void returns nothing. There is no task, so there is nowhere to put the exception. The compiler instead posts it to the current SynchronizationContext, and in ASP.NET Core there isn't one — so it lands on the thread pool as an unobserved exception and terminates the process.

// Wrong: the exception escapes and kills the process
public async void SendWelcomeEmail(string address)
{
    await _mailer.SendAsync(address);   // throws → process down
}

// Right: the caller can await, observe, and handle
public async Task SendWelcomeEmailAsync(string address)
{
    await _mailer.SendAsync(address);
}

The catch block that never runs

This is the part that surprises people:

try
{
    SendWelcomeEmail(address);   // returns immediately
}
catch (SmtpException)
{
    // never reached — the method returned before it threw
}

The call returns at the first await, long before the exception exists. The catch has already gone out of scope by the time anything goes wrong.

The one legitimate exception

Event handlers. An event signature is void, so async void is the only option:

button.Click += async (sender, args) => await LoadDataAsync();

Even here, wrap the body in try/catch yourself. Nothing else will.

What to do instead

  • Return Task from every async method that isn't an event handler
  • For fire-and-forget work, use a hosted service or a queue, not a discarded task
  • Enable the analyzer: VSTHRD100 flags async void at build time
  • See the official guidance on async best practices for the full picture

If you're auditing an existing codebase, search for async void and expect most hits to be bugs nobody has triggered yet.

Found a problem on this page, or something that could be clearer?

Goes straight to the author. Not published anywhere.