In this video, I answer the question “Why is async void bad and how do I await a Task in an object constructor?”. Async void is generally considered bad for 3 reasons:
1. You can’t wait for its completion (fire and forget)
2. Any unhandled exceptions will terminate your process (can’t be caught)
3. Difficult to test (see #1 and #2) Async void methods have different error-handling semantics.
When an exception is thrown out of an async Task method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started. However, let’s be honest, there are many areas of our code where an async void will be required. The scenario that immediately come to mind is in the constructor of an object. For example, maybe you want to load data async when your object is first created. You want to load your data async, and let the object creation process continue without blocking any threads. This is normally done with what’s called a “fire and forget” approach. Other scenarios may include the Execute method of an ICommand, or within an event handler.
Your first instinct may be to wrap these async/await calls into an “async void” method, and then just call that method in your constructor, command, or event handler. But, this is not recommended.
Luckily for use, we can use the power of a C# extension method to provide support for not only handling the completion of a Task in an async void scenario, but also handle any exceptions that may occur and provide better unit testing support.
I like the solution, but I would have added one thing. If you are able to async/await the whole stack. You should do so first. This example is simplified, and very often you would be stuck in a method deep down your call stack where you need to call a Task. You should then check if you can change the call stack to async await first before applying this solution.
Yes, this was a very simple example and production code will look much different. Be pragmatic and use the proper solution for the issue at hand. Thanks for pointing that out.
Hi, Brian. Thanks for this video. It is very helpful.
Do you have the sample code available somewhere to look at, like on GitHub?
Sorry, I don’t. However, that’s not a bad idea. Thanks for the feedback.
why not use ConfigureAwait(True) for this?
Not sure what you mean. Are you asking why not add ConfigureAwait(true) to the “task” in our extension method? Well, that’s the default behavior of tasks.
Why do you not use the existing ContinueWith method?
this.DoSomething()
.ContinueWith(
t => {
if (t.IsFaulted) throw t.Exception;
}
);