Async Patterns and Anti-Patterns

22 March 2013

Introduction

Almost exactly a year ago, I wrote an introductory post on the use of async/await. Since then, doing asynchronous coding using async/await has become the norm, although I still encounter confusion related to their use on forums. In particular, it's a common belief that async tasks always run on a new thread (they don't). If you take a look at Lucien Wischik's excellent collection of async-related videos, he deals with all the common misconceptions in a very clear and concise manner. Stephen Cleary also has an excellent article on the subject in the March 2013 issue of the MSDN magazine.

After recently reading Nigel Sampson's blog on Simple golden rules for async / await, I was prompted to review my own use of async coding patterns. Not because I was doing anything particularly wrong (although I hadn't really grasped why async void was such a bad idea), but because his "golden rules" challenged some of my understanding of exactly what was going on.

So, this post is my interpretation of Nigel's golden rules, along with some experimental code to illustrate important points.

Summary of Patterns and Anti-Patterns

Before getting into the details, let's try and summarize a small number of important patterns and anti-patterns related to async coding. We'll then move on to consider each point in detail:

Do...  
avoid async void methods
  • async void = "fire-and-forget" (no way of knowing when an async op is complete)
  • exceptions can't be caught in the calling method - they crash Win8 apps
  • check lambdas - they may be async void
add exception handling inside async void event handlers
  • you can't avoid async void for event handlers
  • consider encapsulating events as tasks (but it adds complexity)
return Task or Task<T> from async methods
  • exceptions are encapsulated by the Task and surface where the async method was called
await all async operations
  • or they become fire-and-forget
  • the Task silently swallows any exceptions, which are then "lost"
run CPU-bound tasks on a new thread (Task.Run)
  • IO-bound tasks should be run async
  • async != multi-thread

Avoid async void methods

The main point to emphasize is:

Do not use async void methods!

Async methods returning void can cause some nasty issues, as we'll discover. And yet you see them all over the place in code examples and snippets on the web. The only valid use of async void is with event handlers and similar constructs (e.g. like ICommand-based handlers), and even here there you need to be aware of the issues (see below).

The reason async void methods are a bad idea is neatly demonstrated by the following code snippet from a Windows 8 Store app:

public sealed partial class BasicPage : AsyncAwaitDemo.Common.LayoutAwarePage
{
    private string _json;
    public BasicPage() { this.InitializeComponent(); }

    private void OnClickMeTapped(object sender, TappedRoutedEventArgs e)
    {
        try
        {
            GetDataAsync();  // Call an async method synchronously
        }
        catch
        {
            // We *THINK* this'll catch any exception that happens inside the 
            // GetDataAsync() method - but it doesn't ... the app crashes and 
            // the exception is not caught
            TextBlock1.Text = "Error!";
        }
    }

    private async void GetDataAsync()
    {
        var client = new HttpClient();

        // Request data asynchronously
        _json = await client.GetStringAsync("http://NonExistentServer/NoWay");  
        
        var list = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Person>>(_json);
        ListView1.ItemsSource = list;  // Display the people in a ListView 
    }
}

In the above code we've got a method - GetDataAsync() - which returns void and is marked as async. In the OnClickMeTapped() handler we call GetDataAsync() from inside a try/catch block, which we assume will catch any exceptions thrown. In fact, the exception thrown when we attempt to read data from a non-existent server is never caught. This is because by the time the exception is thrown, execution has already left the OnClickMeTapped handler - it's "too late"!

The following diagram shows the situation:

In Windows 8 apps, an uncaught exception causes the app's process to be terminated - the app crashes without any error being displayed to the user. This is why having async void methods is such a bad idea. The only way in which we can catch the exception is by having a try/catch block within the method itself:

private async void GetDataAsync()
{
    var client = new HttpClient();

    try
    {
        // Request data asynchronously
        _json = await client.GetStringAsync("http://NonExistentServer/NoWay");

        var list = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Person>>(_json);
        ListView1.ItemsSource = list; // Display the people in a ListView 
    }
    catch
    {
        TextBlock1.Text = "Error!";
    }
}

Such an approach complicates code, and it's normally desirable to have a higher-level method (e.g. the OnClickMeTapped handler in our case) catch exceptions for lower-level methods and provide common error handling, logging, user notification, etc.

Always return Task or Task<T> from async methods

If we modify our example code slightly, by returning Task from GetDataAsync() instead of void, and by asynchronously calling GetDataAsync() from the event handler, we avoid all of the previous problems:

private async void OnClickMeTapped(object sender, TappedRoutedEventArgs e)
{
    try
    {
        await GetDataAsync();  // Call async
    }
    catch
    {
        // This catches any exceptions in GetDataAsync()
        TextBlock1.Text = "Error!";
    }
}

private async Task GetDataAsync()
{
    var client = new HttpClient();

    // Request data asynchronously
    _json = await client.GetStringAsync("http://NonExistentServer/NoWay");  
    
    var list = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Person>>(_json);
    ListView1.ItemsSource = list;  // Display the people in a ListView 
}

Now, when any exceptions are thrown they ARE caught by the try/catch block. This is because when an exception happens in an async method returning Task or Task<T>, it is caught (by the state machine code generated by the compiler to support the async method) and placed into the Task object's Exception property.

In our example, the exception surfaces at the await GetDataAsync() statement, when the Task object is "unwrapped". This means our try/catch block can catch and process exceptions in a normal manner.

This also explains why exceptions thrown in async void methods are "un-catchable": there's no Task object to receive the exception, so it's raised directly on the SynchronizationContext used when the method was called - this is normally the UI context.

As can be seen in the above image:

  1. An async call is made to GetDataAsync(), which runs synchronously until the first await statement in encountered
  2. Control then returns to the OnClickMeTapped handler...
  3. ...which itself returns control to its caller (the UI)
  4. At some point the GetStringAsync() method completes, and the UI thread's message pump restores control to the next statement following the await in GetDataAsync()
  5. Execution of the GetDataAsync() method then continues normally until it returns. Control then passes back to the next statement in the OnClickMeTapped handler

If an exception is thrown in GetDataAsync(), control passes back to the OnClickMeTapped handler where it is caught.

An even better way to re-write the above code is to have GetDataAsync() return Task<T>:

public sealed partial class BasicPage : AsyncAwaitDemo.Common.LayoutAwarePage
{
    public BasicPage() { this.InitializeComponent(); }

    private async void OnClickMeTapped(object sender, TappedRoutedEventArgs e)
    {
        try
        {
            ListView1.ItemsSource = await GetDataAsync();  
        }
        catch
        {
            TextBlock1.Text = "Error!";
        }
    }

    private async Task<List<Person>>  GetDataAsync()
    {
        var client = new HttpClient();
        var json = await client.GetStringAsync("http://localhost/MvcWebApiDemo/api/Person");
        
        return Newtonsoft.Json.JsonConvert.DeserializeObject<List<Person>>(json);
    }
}

Check lambdas - they may be async void

Async lambda expressions can hide the fact that you're actually creating a delegate for a void - an async void in other words, with all the attendant issues we've already covered.

Consider the following code snippet from a Windows 8 app:

protected override void LoadState(Object navigationParameter, Dictionary<String, Object> pageState)
{
    try
    {
        ClickMeButton.Tapped += async (sender, args) =>
        {
            var client = new HttpClient();
            var json = await client.GetStringAsync("http://localhost/DoesNotExist");
            ListView1.ItemsSource = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Person>>(json);
        };
    }
    catch (Exception ex)
    {
        // This won’t catch exceptions!
        TextBlock1.Text = ex.Message;
    }
}

Here we're creating an async lambda expression to handle the Tapped event for a button. What in effect we've done is to create an async void method just like our first example. Again, it looks like we'll catch any exceptions thrown inside the lambda. However, as with our first example, execution falls out of the LoadState method before it gets a chance to catch any exceptions.

As Lucien Wischik points out, whenever you see an async lambda, always check the what the delegate type is - if it's void, apply the rules about async void methods.

The only way to check if a lambda's void is by looking at the definition of the type the lambda will be assigned to. This is easy to do in Visual Studio: using the above example, put the cursor on .Tapped and press F12. If you do this you'll see that it's defined as a TappedEventHandler:

public event TappedEventHandler Tapped;

And if you view the definition of TappedEventHandler, you'll clearly see it's a delegate for a void method:

/// <summary>
/// Represents the method that will handle the Tapped event.
/// </summary>
[WebHostHidden]
[Version(100794368)]
[Guid(1759068364, 40944, 18894, 177, 65, 63, 7, 236, 71, 123, 151)]
public delegate void TappedEventHandler(object sender, TappedRoutedEventArgs e);

In the above case we could have guessed that TappedEventHandler would required a void delegate, after all, it's an event, and all events have void return types. Other cases may not be so obvious, so always check.

What to do about event handlers and other async voids

Having identified that an event handler, lambda, or a method you need to use with async code is a void, what can you do about it? Well, there are a few options, but probably the best idea is to be aware of what happens with async voids (inability to determine when an async op completes; unable to catch exceptions in the calling method) and code "defensively":

  • Use with care, and only for top-level event handlers and methods
  • Add exception handling inside the body of the async method to catch exceptions - you won’t be able to (reliably) catch exceptions in the calling method
  • You'll still have the problem of "fire-and-forget" (no way to know when an async task completes)

In his blog, Lucien Wischik describes how to go about encapsulating events as tasks. However, to my mind, this adds complexity and starts to detract from the advantages of the simple async/await Task-based pattern.

Await all async operations

Whenever you call an async method returning a Task or Task<T>, you should always await the result. If you don't, the call becomes fire-and-forget. Also, exceptions are silently swallowed by the Task:

private async Task GetDataAsync()
{
    var client = new HttpClient();

    // When this statement throws an exception it is silently swallowed by the Task object
    var json = await client.GetStringAsync("http://localhost/MvcWebApiDemo/api/Personxxxx");
    ListView1.ItemsSource = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Person>>(json);
}

private async void ClickMeButtonTapped(object sender, TappedRoutedEventArgs e)
{
    try
    {
        GetDataAsync();  // Sync call - the method's Task return swallows exceptions
                         // Because we're not awaiting the Task return we lose the exception
    }
    catch (Exception ex)
    {
        // Exceptions are NOT caught...
        TextBlock1.Text = ex.Message;
    }
}

Run CPU-bound tasks on a new thread

Async is for IO-bound code

Everything we've seen so far effectively concerns the scheduling of "chunks of work" (tasks), all of which run on the same thread. It's important to remember that the use of async/await does NOT result in additional threads being created. Behind the scenes, the compiler creates a callback-based mechanism for you, and handles breaking-up the code for your async method into chunks. When your async method executes, it runs on the same thread (SynchronizationContext) as the calling code.

The SynchronizationContext, which is essentially a message queue that can start, stop and manage jobs, and receive notifications when they're complete, schedules time for your async code to run on the UI thread only when required (e.g. rather like pre-emptive multi-tasking at the OS-level). Because it's running on the UI thread, async code can update the UI. This helps to make Task-based async coding feel natural and straightforward.

The use of async/await is designed to asynchronously run code which would otherwise be IO-bound ("blocking"). It's NOT meant for running multiple computational (CPU-bound) operations "in parallel".

Task.Run is for CPU-bound code

The use of Task.Run() DOES create a new background thread. However, because it's not running on the UI thread, code started with Task.Run() cannot update the UI without using synchronization techniques:

public sealed partial class BasicPage : AsyncAwaitDemo.Common.LayoutAwarePage
{
    private SynchronizationContext _ctx;

    public BasicPage()
    {
        this.InitializeComponent();

        _ctx = SynchronizationContext.Current;
    }

    private async void ClickMeButtonTapped(object sender, TappedRoutedEventArgs e)
    {
        try
        {
            await Task.Run(() => 
            {
                var ia = new int[1000];
                for (var i = 0; i < 1000; i++)
                    ia[i] = i;

                var canUpdateUI = this.Dispatcher.HasThreadAccess; // => false

                // Throws an exception "wrong thread")
                TextBlock1.Text = "Loop done!";

                // If you want to update the UI from a different thread...
                _ctx.Post((object state) => 
                {
                    canUpdateUI = this.Dispatcher.HasThreadAccess; // => true
                    TextBlock1.Text = "Loop done"; 
                }, null);
            });
        }
        catch (Exception ex)
        {
            TextBlock1.Text = ex.Message;
        }
    }
}

Summary of my async best practices

The following points summarize my own "golden-rules" for async coding:

  • avoid async void methods
  • add exception handling inside async void event handlers
  • check async lambda types - if they are void, treat as for async void handlers
  • return Task or Task<T> from async methods
  • await all async operations
  • use await on IO-bound (blocking) operations
  • use Task.Run to execute CPU-bound operations on a new thread