.NET 4.5 - Async and Await

10 March 2012 (updated 16 February, 2013)

Summary:
With .NET 4.5 we have a new, less complex, model for performing asynchronous operations. The new Task-based Asynchronous Pattern (TAP) encourages an asynchronous approach to initiating potentially lengthy operations, while retaining the syntactical simplicity of a synchronous model. This is achieved through the async and await keywords, and the supporting Task and Task<T> types.

Multiple Threads and the UI

Consider a typical problem scenario: A C# WPF app needs to gets the contents of a file from a remote web server (or some other, lengthy operation). If the operation takes more than a fraction of a second the UI appears (to the user) to lock-up. To get around this, the developer kicks off a new thread to get the file. When the operation completes, a callback method gets called, at which point we want to give a visual clue to the user that the file's content has been downloaded. But, the UI is running on a single thread (the so-called STA, or Single Threaded Apartment model), so we can't simply access any UI components to announce the fact. To access the UI's thread, we have to perform some expensive thread synchronization magic.

The following simple example demonstrates the scenario. Here we have a WPF app with a single button (that starts the thread) and a listbox:

    public partial class MainWindow : Window
    {
        private Action m_thread;
        
        private void buttonThread_Click(object sender, RoutedEventArgs e)
        {
            buttonThread.IsEnabled = false;
            listBoxLog.Items.Add("New Thread Started");

            m_thread = StartThread;
            m_thread.BeginInvoke(EndThread, null);  // Kick-off a new thread  
        }

        private void StartThread()
        {
            // Simulate a lengthy op by sleeping the new thread for 1 sec
            System.Threading.Thread.Sleep(1000);
        }

        private void EndThread(IAsyncResult result)
        {
            // Update the UI thread
            this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
            (Action)(() =>
            {
                listBoxLog.Items.Add("Thread Ended");
                buttonThread.IsEnabled = true;
            }));
        }
    }

When we start the new thread with BeginInvoke, we specify a callback (EndThread) that will be called when the thread completes its work. Notice the use of the app's single Dispatcher mechanism (which, essentially, encapsulates the WPF message loop) in EndThread to call from the new thread into the UI thread.

The Task-based Async Pattern

The problem with the above callback-based model is just that, the callback, which introduces added complexity to the programming model: our logical flow of operation suddenly disappears, waiting to be kick-started via the callback. As Eric Lippert says, in his article on asynchrony in the MSDN magazine:

The price you pay for responsiveness and performance [when using multi-threading] is that you have to write code that emphasizes how the mechanisms of the asynchrony work while obscuring the meaning and purpose of the code.

Starting with .NET 4.5, support is introduced for a simplified asynchronous model through the use of the async and await keywords, and the Task and Task<T> types. This allows the developer to mark a method as async which can await the outcome of some lengthy operation. The call to the async method returns immediately, thus preserving the appearance of a synchronous program flow, and essentially leaving the async method to complete its work in its own sweet time:

private void DoTaskRun(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
    DoLongRunningTasks();

    // Rest of the method carries on running immediately
    :
    :
}

private async void DoLongRunningTasks()
{
    await ThisTakesALongTimeAsync();
    await ThisTakesEventLongerAsync();
}

It's important to note two points here:

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

    Because it's running on the UI thread, async code can update the UI
  2. The use of Task.Run(...) DOES create a new background thread

    Because it's not running on the UI thread, code started with Task.Run() cannot update the UI without using synchronization (see below)

The following simple WinRT app demonstrates some of the principles outlined above. The UI consists of a just single button and listbox:

When the button is clicked, the async method is called and then immediately returns. At sometime later, the loop completes, as shown by the output:

Examining the async method, we see that the async keyword precedes the return (void) type, and that the main body of the method consists of:

await Task.Run(lambda expr);

The Task<T> type may be regarded as the encapsulation of a process that will do work and produce a result of type T at some (indeterminate) time in the future. Our example above makes use of Task, which encapsulates a process that will do some work that produces no result.

Both Task<T> and Task are used to create awaitable operations. Under the covers, when our async method is called, the expression given to Task.Run() is evaluated as an object that will complete at some time in the future, and the remainder of the method (e.g. listBoxLog.Items.Add()) is added to the 'hidden' callback that is associated with the completion of the Task.

The following shows how we can develop the example to cope with async processes that produce results. As shown below, our example method asynchronously requests data from a URI:

Notice that the DoAsyncAwait() method returns immediately after it calls await GetDataAsync(). The operation to get the data completes "sometime" later, at which point the data's assigned to the result variable in GetDataAsync(), and the flow of execution continues where it left off previously.

Contrast the above code with the very similar code below. All we've done is to make a synchronous call to GetDataAsync() from DoAsyncAwait(). When the await statement is hit in GetDataAsync(), execution returns immediately and the async getting of data completes at some point later:

The behind-the-scenes mechanism for implementing the async/await pattern is rather complex, and involves the construction of a 'state machine', along with a fair bit of plumbing. Those interested in the details can review an article on the subject by Mads Torgersen in the MSDN magazine. As app developers, we can mostly ignore the implementation details and simply use the pattern. That, after all, is the whole point of introducing the async and await keywords to C# and Visual Basic.

However, we still need to be aware of certain restrictions, particulalrly when creating a new thread via Task.Run. For example, if we modify the previous example to try and update the UI in the middle of processing the Task.Run() expression we'll get an exception:

If we really need to update the UI thread, we can use a variant of the dispatcher 'trick' used in our previous WPF app. This time we see how to use the System.Threading.SynchronizationContext type to synchronize between the async code running on a new thread and the UI thread:

For lots more resources on async programming, refer to the dedicated MSDN page: http://msdn.microsoft.com/en-us/async