Why async void is dangerous

1 min read

An async methodAsync and awaitAwait frees the thread while an operation waits, and the method picks up where it left off.Opens in a new tab normally returns a Task, which the caller can await, chain onto or catch exceptions from. async void returns nothing, so none of that is possible.

The legitimate use

UI event handlers. EventHandler returns void (see built-in delegate typesDelegatesA type-safe function pointer, so a method can be passed around like any other value.Opens in a new tab), so a handler that awaits has to be async void.

C#
private async void Button_Click(object sender, EventArgs e){    label.Text = "loading...";    var data = await httpClient.GetStringAsync(url);    label.Text = data;}

Problems

The caller continues at once and cannot tell when the work finishes, and the method cannot be passed to Task.WhenAllTask combinatorsWait for all of several tasks, or just the first one to finish.Opens in a new tab or waited on in a test.

Exceptions escape. In an async Task method they are stored in the Task. In an async void method they are rethrown on the SynchronizationContext that was current at the start, or on a pool thread if there is none, so they are unhandled and typically crash the process. A try/catch around the call cannot catch them, because the method has returned at its first await.

C#
try{    FireAndForget(); // async void}catch (Exception ex){    // This will never catch an exception thrown after an await inside FireAndForget} async void FireAndForget(){    await Task.Delay(100);    throw new Exception("boom"); // surfaces later, on a different context, and skips the try/catch above}

Rule

Use async Task by default. Keep async void for top-level event handlers, with the body in its own try/catch. Anywhere else it is a bug waiting to happen (see async pitfallsAsync pitfallsFour things to watch for when writing async code.Opens in a new tab).

C#
private async void Button_Click(object sender, EventArgs e){    try    {        await DoWorkAsync();    }    catch (Exception ex)    {        // handle/log here: this is your only real chance to catch it    }}