async void
methods in C# are a source of a lot of problems for many developers getting into writing async await code. The pattern that we’re suggested to use is of course async Task
, but there are cases — like with Event Handlers in C# — where the method signatures just aren’t compatible.
In this article, I’ll explain why async void
methods in C# are something you want to avoid. We’ll cover some code examples that compare async void
and async Task
for better understanding, and I’ll also explain what you should do if you have no other choice but async void
.
What’s In This Article: async void
Remember to check out these platforms:
What Are async void Methods in C#?
In C#, async void
methods are a way to define asynchronous methods that don’t return a value. These methods are typically used for event handlers or other scenarios where the method signature being enforced does not support a Task
return type and instead void
is enforced.
async void
methods are defined by using the async
keyword before the method signature, followed by the return type void
. For example:
public async void SomeMethod()
{
// Code here
}
When compared to regular asynchronous methods that return a Task
or Task<T>
, there are a few important differences to note. And when I say “important” I mean “You really need to avoid this as much as you humanly can so you save yourself some headaches”.
Differences Between async void And async Task in CSharp
One of the main differences between async void
methods and async Task
methods lies in how exceptions are handled.
In async Task
methods, any exceptions that occur are captured by the returned Task
object. This allows the calling code to handle the exception or await the Task
to observe any exceptions later. This is how the entire async await infrastructure has been built in C#. It’s often why you’ll see async await get introduced into a codebase and then all of the calling code starts getting converted over to also be async await — You really DO want it there.
On the other hand, async void
methods cannot be awaited directly, and any exceptions that occur within them will be bubbled up to… where? The SynchronizationContext that started the async method in the first place. Even Stephen Cleary from Microsoft mentions in his article:
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. Figure 2 illustrates that exceptions thrown from async void methods can’t be caught naturally.
Stephen Cleary
Code Example of async Task vs async void Methods in C#
It’ll be helpful to compare two variations of the same layout of code using each of these patterns to see how the issues can arise. Consider the following example that uses async Task:
public async Task ProcessDataAsync()
{
// Some asynchronous operations
}
public async void HandleButtonClick(object sender, EventArgs e)
{
try
{
await ProcessDataAsync();
}
catch (Exception ex)
{
// Handle the exception
}
}
In this code, if an exception occurs in the ProcessDataAsync
event handler method, it will be caught by the try-catch block in the HandleButtonClick
method and can be appropriately handled. However, if ProcessDataAsync
is defined as
instead, any exceptions thrown by async void
ProcessDataAsync
would bypass the catch block in the HandleButtonClick
event handler method and potentially crash the application:
public async void ProcessDataAsync()
{
// Some asynchronous operations
}
public async void HandleButtonClick(object sender, EventArgs e)
{
try
{
ProcessDataAsync();
}
catch (Exception ex)
{
// This will never catch the async exceptions!
}
}
The Dangers of async void Methods in C#
The common theme that you’re hopefully noticing in this article is that async void
methods in C# are dangerous and something you should be trying to avoid. Here’s a list of challenges that we get with async void
methods, to hopefully steer you away from using them (unless you have no choice):
- Error Propagation:
async void
methods do not allow errors to be caught or propagated. When an exception occurs within such a method, it escapes to the synchronization context, often resulting in an unhandled exception that can crash the application. - Awaiting Behavior: Unlike
async Task
methods,async void
methods cannot be awaited. This can lead to issues with controlling the flow of asynchronous operations, potentially causing race conditions or executing operations out of the intended order. - Debugging Difficulty: Debugging exceptions in
async void
methods is more challenging because the call stack may not accurately represent the execution flow at the time the exception was thrown, complicating the process of identifying and fixing bugs.
Best Practices for Handling async void Methods in C#
When working with async void methods in C#, it is important to be aware of their potential dangers and follow best practices to ensure a robust and reliable codebase. Here are some recommendations for handling async void methods cautiously:
- Avoid
async void
whenever possible:async void
methods should generally be avoided, especially in scenarios where exception handling and error recovery are critical. While async void methods may appear convenient, they lack the ability to propagate exceptions and can result in unpredictable program behavior. Instead, consider usingasync Task
methods, which provide better error-handling capabilities. - Use
async Task
instead: By using theasync Task
return type for asynchronous methods, you can take advantage of the Task’s built-in exception handling mechanism. This enables you to catch and handle exceptions appropriately, ensuring that your code maintains control over the execution flow. Usingasync Task
methods also allows for better code maintainability, testability, and fewer mysterious problems thanasync void
. - Handle exceptions in
async void
methods: If you must useasync void
methods, it is important to properly handle exceptions to prevent them from silently propagating and causing unexpected system behavior. One way to achieve this is by enclosing the code within a try/catch block. Within the catch block, you can log the exception and handle it accordingly, such as displaying an error message to the user or rolling back any relevant operations. - Log and monitor asynchronous operations: When working with
async void
methods, logging and monitoring become even more critical. Since these methods do not have a return type, it becomes challenging to determine their completion or identify any potential issues. Implementing a robust logging and monitoring system, such as using a logging framework like Serilog or utilizing application insights, can help track the progress and status of your asynchronous operations, aiding in debugging and troubleshooting.
Try/Catch for async void Methods in C#
I’ve written about several different ways to try working with this kind of code before, but ultimately it feels like making sure you wrap every async void
method body in try/catch is the most straightforward. Maybe someone can create a Rosyln analyzer to enforce this?
Here’s an example demonstrating how you can use a try-catch around the entire body of code that is async void
:
public async void DoSomethingAsync()
{
try
{
// Perform asynchronous operations
await Task.Delay(1000);
await SomeAsyncMethod();
}
catch (Exception ex)
{
// TODO: ... whatever you need to do to properly report
// on issues in your async void calls so that you
// can debug them more effectively.
// Log the exception and handle it appropriately
Logger.Error(ex, "An error occurred while executing DoSomethingAsync");
// Display an error message or take necessary action
DisplayErrorMessage("Oops! Something went wrong. Please try again later.");
}
}
By adhering to these best practices, you can mitigate the risks associated with async void
methods and avoid a pile of neverending headaches. Remember to prioritize using async Task
methods whenever possible for better exception handling and control over the asynchronous execution flow — you really don’t want to add async void
anywhere unless it’s an absolute last resort.
Wrapping up async void Methods in C#
In conclusion, async void
methods in C# can be dangerous for several reasons, and you’ll want to prioritize NOT using them. There are unfortunate situations where APIs and method signatures do not line up, such as Event Handlers, but otherwise, do your best to avoid these.
By using async void
, we lose the ability to await or handle exceptions properly. This can lead to unhandled exceptions and unexpected behavior in our code. To avoid these hazards, I recommend that you use async Task
for methods that are intended to be asynchronous wherever you possibly can. This allows us to await the result, handle exceptions, and have better control over our code’s execution flow. It promotes better error handling and improves overall code quality. When you have no other choice, make sure you are wrapping your entire async void method in a try/catch and investing in proper error handling/logging/reporting.
Approach async programming with caution and continuously seek to enhance your understanding of the topic! If you found this useful and you’re looking for more learning opportunities, consider subscribing to my free weekly software engineering newsletter and check out my free videos on YouTube! Check out my Discord community if you want to connect with me and other software engineers on these types of topics!
Frequently Asked Questions: async void Methods in C#
What are async void methods?
Async void methods in C# are methods that use the async keyword but have a return type of void. They are typically used for event handlers or top-level entry points.
What is the difference between async void and async Task methods?
The main difference is in how exceptions are handled. Async void methods don’t allow for proper exception handling and can crash the application if exceptions are not caught. Async Task methods, on the other hand, allow for better error handling and exception propagation.
What are the dangers of using async void methods?
Using async void methods can lead to unhandled exceptions, which can cause application crashes. The lack of error handling and exception propagation makes it difficult to handle and recover from failures.
Why should async Task be preferred over async void?
Async Task methods provide better control over error handling and exception propagation. They also allow for easier composition of asynchronous operations and make it easier to handle and recover from failures.
What are the best practices for handling async void methods?
It is recommended to avoid using async void methods whenever possible. Instead, use async Task methods. If async void methods must be used, proper exception handling using try/catch blocks should be implemented, and logging and monitoring of asynchronous operations should be in place.
I think this sentence “However, if the HandleButtonClick event handler method is defined as async void instead” is wrong. I think you meant to refer to ProcessDataAsync, correct?
You’re totally write – Unfortunately both of these methods in the second form are async void (and both would be trash to work with haha). But pertaining to what’s being described, what you pointed out is correct. Thanks!