DEV Community

Cover image for async void Methods In C# – The Dangers That You Need to Know
Dev Leader
Dev Leader

Posted on • Originally published at devleader.ca

async void Methods In C# – The Dangers That You Need to Know

The post async void Methods In C# – The Dangers That You Need to Know appeared first on Dev Leader.

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 Are async void Methods in CSharp?

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
}


Enter fullscreen mode Exit fullscreen mode

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”.

Dev Leader Weekly | Substack

My weekly newsletter that simplifies software engineering for you, along with C# code examples. Join thousands of software engineers from companies like Microsoft and Amazon who are already reading! Click to read Dev Leader Weekly, a Substack publication with thousands of subscribers.

favicon weekly.devleader.ca

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 CSharp

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
    }
}


Enter fullscreen mode Exit fullscreen mode

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 the ProcessDataAsync method is defined as `async void` instead, any exceptions thrown by 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!
    }
}


Enter fullscreen mode Exit fullscreen mode

The Dangers of async void Methods in CSharp

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):

  1. 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.

  2. 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.

  3. 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 CSharp

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:

  1. 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 using async Task methods, which provide better error-handling capabilities.

  2. Use async Task instead: By using the async 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. Using async Task methods also allows for better code maintainability, testability, and fewer mysterious problems than async void.

  3. Handle exceptions in async void methods: If you must use async 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.

  4. 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 CSharp

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.");
    }
}


Enter fullscreen mode Exit fullscreen mode

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 CSharp

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!

Dev Leader Weekly | Substack

My weekly newsletter that simplifies software engineering for you, along with C# code examples. Join thousands of software engineers from companies like Microsoft and Amazon who are already reading! Click to read Dev Leader Weekly, a Substack publication with thousands of subscribers.

favicon weekly.devleader.ca

Want More Dev Leader Content?

  • Follow along on this platform if you haven’t already!
  • Subscribe to my free weekly software engineering and dotnet-focused newsletter. I include exclusive articles and early access to videos: SUBSCRIBE FOR FREE
  • Looking for courses? Check out my offerings: VIEW COURSES
  • E-Books & other resources: VIEW RESOURCES
  • Watch hundreds of full-length videos on my YouTube channel: VISIT CHANNEL
  • Visit my website for hundreds of articles on various software engineering topics (including code snippets): VISIT WEBSITE
  • Check out the repository with many code examples from my articles and videos on GitHub: VIEW REPOSITORY

Top comments (0)