I have been using the Task-based asynchronous programming model introduced in C# 5 (.NET 4.5) for a while now and it truly changed my life (at least as far as writing async code is concerned!). No more callbacks and non-linear flow. The code we write with the async/await paradigm is very similar to what we would write in a synchronously fashion which makes it far easier to follow and understand.

There is a tone of documentation and articles out there on how to use asynchronous programming. So much documentation in-fact, that some key features can be easily overseen. There is one feature especially that I only discovered by coincidence a few weeks ago: ConfigureAwait.

Synchronization Context

When an incompleted Task is awaited, the current context is captured and used to resume the method when the task is completed (e.g. after the await keywords). The context is null if called from a thread that is not the UI thread. Otherwise it returns the UI specific context depending on the platform you are using (WinForm, WPF, ASP.NET have different implementations)

This constant context switch between UI thread context and worker thread context can cause performance issues. Your app may feel less responsive as the amount of asynchronous code that you are writing grows…which is exactly what we are trying to avoid when writing asynchronous code.

ConfigureAwait(continueOnCapturedContext:false)

By using the ConfigureAwait defined in the Task class and passing it the value continueOnCapturedContext:false, you can enable some parallelism and ensure that code can continue working on a worker thread instead of being marshaled back to the original context and potentially be executed on the UI thread

There are a few rules to know to avoid any surprises:

With this rules in mind, my suggestion is to always separate the context-dependent code from the context-free code. The goal is to minimize the context-dependent (typically event handlers) code by creating methods, which a context of their own.

Let’s take the following code (all in one file for simplicity), simulating the update of a ListView from of an hypothetical list of songs:

//Event handler
private async void onRefreshClicked(object sender, EventArgs e)
{
    try
    {
        //Show the refresh indicator of the list view
        listView.BeginRefresh();
        
        //Download songs from server 
        var json = await SongAPI.DownloadAllSongs();
        
        //deserialize json to songs and perform some non UI tasks with those songs
        var songs = JsonConvert.DeserializeObject<List<Sing>>(json);
        performSomeTaskWithListOfSongs(songs);
        
        //update the UI
        updateListOfSongs(songs);
    }
    finally
    {
        //Hide the refresh indicator of the list view
        listView.EndRefresh();
    }
     
} 

This code could be updated to follow our best-practices like so:

//Event handler
private async void onRefreshClicked(object sender, EventArgs e)
{
    try
    {
        //Show the refresh indicator of the list view
        listView.BeginRefresh();
        
        var songs = await downloadSongs();
        
        //update the UI
        updateListOfSongs(songs);
    }
    finally
    {
        //Hide the refresh indicator of the list view
        listView.EndRefresh();
    }
}

private async Task<IReadOnlyList<Song>> downloadSongs()
{
    //Download songs from server 
    var json = await SongAPI.DownloadAllSongs()
        .ConfigureAwait(continueOnCapturedContext: false);

    //NEW: This is now done on the worker thread!
    
    //deserialize json to songs and perform some non UI tasks with those songs
    var songs = JsonConvert.DeserializeObject<List<Song>>(json);
    performSomeTaskWithListOfSongs(songs);

    return songs;
}

Another option would be to switch back to the UI-thread, only when UI updates are required and stay on the worker thread(s) otherwise. A typical scenario would be to display a progress bar showing the number of tasks completed, while those tasks are being processed in the background. We will explore different options for switching to the UI-thread in a future post.