Quote of the Day

more Quotes

Categories

Buy me a coffee

Async/Await beginner mistake: Using async void in non event handler.

Published November 4, 2018 in Asynchronous programming , C# - 0 Comments

In this post, I share some bits I have learned about the async/await pattern. Specifically, I discuss some of the pitfalls of using async void in non event handlers.

You don’t get the benefits of Task-Based Asynchronous Pattern (TAP).

When you call an async void method, you cannot await it because the await keyword is applicable on a Task, not on a method that returns nothing.

From the C# reference on await, 

The task to which the await operator is applied typically is returned by a call to a method that implements the Task-Based Asynchronous Pattern. They include methods that return TaskTask<TResult>, and System.Threading.Tasks.ValueType<TResult> objects.

One of the downsides is you cannot take advantage of the built-in continuation mechanisms such as WhenAll(), WhenAny(), ContinueWith() etc …

For instance, a common pattern of implementing timeout is using WhenAny() together with Task.Delay().

Task timeoutTask = Task.Delay(delay: TimeSpan.FromSeconds(30));
Task saveTheWorldTask = SaveTheWorld();
Task saveTheWorldWithTimeout = Task.WhenAny(timeoutTask, saveTheWorldTask);
if (saveTheWorldWithTimeout == timeoutTask)
{
    throw OperationCanceledException("Timed out while saving the world.!");
}
else
{
    // Continue after saving the world. 
}

To use WhenAny(), the method must return a Task. As such, you cannot use it with an async void method.

Operations that depend on previous operations to have been completed may not work.

One of the benefits the async await pattern brings is the ability to fine tune the level of concurrency. Basically, the async await pattern allows you to run codes synchronously until the first await expression. If you need a task to finish before moving on, you simply await (asynchronously wait) or wait (synchronously wait) for it. When you want to execute multiple tasks at the same time, you simply declare them first, and await them all without blocking. At the high level, you can define the overall flow in a synchronous manner while still able to take advantage of concurrency without much boilerplate codes.

private async Task DownloadFilesAsync(string[] urls)
 {
     List<Task<byte[]>> downloadTasks = new List<Task<byte[]>>(); 
     // asynchronously download all the files
     foreach (string url in urls)
     {
         downloadTasks.Add(DownloadFileAsync());
     }
     // wait until all the files have been downloaded. 
     byte[][] downloadedFiles = await Task.WhenAll(downloadTasks);
     // do something with the files 
     byte[] compressedFiles = ZipFiles(downloadFiles);
     // .... 
 }

You see, it is easy to use async/await. However, it is also easy to make the mistake of assuming an asynchronous operation has finished before moving on to the next one.

Consider the example below:

private CloudQueue _newOrderQueue;

private async void InitQueue()
{
    __newOrderQueue = _queueClient.GetQueueReference("NewOrderQueue");
    await _newOrderQueue.CreateIfNotExistsAsync();
}

private async void QueueOrder(Order newOrder);
{
    if (_newOrderQueue == null)
    {
        InitQueue(); 
    }
    CloudQueueMessage orderMessage = new CloudQueueMessage(newOrder.AsString());

    CloudQueueMessage message = await _newOrderQueue.AddMessageAsync(orderMessage);
    // do other things like sending email to customer 

}

In the above example, the method checks and initializes an azure queue first before pushing a message to the queue.  It is easy to make the mistake of declaring the method with async void for two reasons. First, the method does not need to return anything since it just initializes the _newOrderQueue instance variable. Second, the method either needs to be declared with async or block on the call CreateIfNotExistsAsync().

Because InitQueue() is asynchronous, execution returns back to QueueOrder() as soon as it reaches the await expression. However, we don’t have a simple way of knowing whether the InitQueue() has finished or not since we cannot await it. If the InitQueue() has not finished creating the queue, the call to AddMessageAsync() will fail.

In the above example, the QueueOrder should have been declared with async Task instead of async void.

From the C# reference on Async Return Types

Async methods can have the following return types:

  • Task<TResult>, for an async method that returns a value.

  • Task, for an async method that performs an operation but returns no value.
  • void, for an event handler.

Remember, if you need to wait for a task to finish before moving to the next one, you have to either await or wait for it. However, you can only await a task; you cannot await a method if it does not return anything. 

Exception in an async void method cannot be caught naturally.

This is probably the most important reason to avoid using async void. If we are lucky, the application crashes; hopefully, that give us some ideas to debug. If we are not lucky, the application may cause havoc when there is an exception and our codes do not catch it.

From the MSDN article on async/await best practices

Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or 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

If you are not familiar with synchronization context, checkout the  MSDN article on synchronization context vs execution context  .

However, in an ASP.NET core application, there is no synchronization context.

In the example below, the application is an ASP.NET core application. When I execute the GET endpoint, I get back the Ok() result even though the async void method throws an exception. Imagine your codes modify some important data in the database and you want to rollback in the case of an exception, using async void may cause your data to be in an inconsistent state as the exception may slip by without you realizing it.

private async void ThrowExceptionAsync()
{
    await Task.Delay(delay TimeSpan.FromMilliseconds(500));
    throw new Exception(); 
}

[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
    try
    {
        ThrowExceptionAsync(); 
    }
    catch (Exception e)
    {
        return new StatusCodeResult(500);
    }
    return Ok("Exception is not caught"); 
}
Async void exception not caught
Async void exception not caught

Recap

You should never use async void unless the method is an event handler. If the method is an event handler,  you simply have to use async void since an event handler must not return anything.

From the async/await best practices article

The exception to this guideline is asynchronous event handlers, which must return void. This exception includes methods that are logically event handlers even if they’re not literally event handlers (for example, ICommand.Execute implementations)

In summary, using async void in non event handler is bad for the following reasons:

  • You cannot await an async/void method.
  • You cannot take advantages of TAP features.
  • If the async void method or any method it calls throw an exception, the exception cannot be caught by a try/catch block.

I hope this post has been helpful. I would love to get your thoughts on this. What are your experiences in using async void? Have you used it in a non event handler method?

Additional Resources

Async/Await – Best Practices in Asynchronous Programming

Task-based Asynchronous Pattern (TAP)

Consuming the task based asynchronous pattern

ExecutionContext vs SynchronizationContext

await (C# reference)

Async Return Types (C# reference)

ASP.NET core synchronization context

No comments yet