BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Parallel and Asynchronous
.NET 4.5+

Async and Await

Asynchronous operations are particularly suited to slow processes that would otherwise block the user interface thread and make applications unresponsive. In the past asynchronous code has been complicated to develop. .NET 4.5 simplifies this greatly.

Async / Await Button

When you are using .NET 4.5, the process for created asynchronous methods is greatly simplified. Two new keywords are added to C#, with similar keywords in Visual Basic, to indicate the use of the Task-Based Asynchronous Pattern (TAP). These are async and await.

The async keyword is a modifier that you apply to a method to indicate that it includes asynchronous elements. A member decorated in this manner is known as an async method. Async methods always have a return type of void, Task or Task<TResult>. Those methods that return void are generally event handlers. Methods that, in their synchronous equivalent, would normally return void return Task instead. Those that need to return a value of type TResult instead return Task<TResult>.

By convention, async methods should be named with an Async suffix; if you have a synchronous method named, "GetTime", the asynchronous version should be named "GetTimeAsync". If you already have such a name in your type, the new async method should use the suffix, "TaskAsync" instead. For example, "GetTimeTaskAsync".

The await keyword can only be used within an async method. It is applied to a Task object, which is often the return value from another async method. Using await relinquishes control to the current method's caller immediately. The task, or async method, is processed asynchronously on another thread. This provides similar functionality to calling BeginInvoke on a delegate. Additionally, the await keyword returns the result of the task. So if an async method returns Task<string>, the await keyword will return a string.

Further code can exist following the await. This appears similar to its synchronous counterpart but is rewritten during compilation to become a continuation of the Task being awaited. The code rewriting even takes care of the problems of cross-thread updates so that you do not need to worry about using Control.Invoke to change the properties of the controls on the form.

Let's take a look at the code that we need to create an asynchronous version of the original synchronous code:

private async void AsyncButton_Click(object sender, EventArgs e)
{
    AsyncButton.Enabled = false;
    AsyncBar.Style = ProgressBarStyle.Marquee;
    AsyncLabel.Text = await GetTimeAsync();
    AsyncBar.Style = ProgressBarStyle.Blocks;
    AsyncButton.Enabled = true;
}

As you can see, the code is remarkably similar to the first sample. There are just two differences. Firstly, the event handler method is decorated with the async keyword to indicate that it includes some asynchronous operations. Secondly, the Text property of the AsyncLabel control is set using the await keyword, applied to the result of a new async method named, "GetTimeAsync".

All of the code of the method up until the call that uses await will run synchronously. When the await is encountered, control will return to the caller of the method. The call to GetTimeAsync will be asynchronous. The two following lines, which reset the progress bar and button, will become a continuation of the asynchronous task. This gives equivalent functionality to the callback but using the Task class, which was introduced in .NET 4.0 as part of the Task Parallel Library.

To complete the final sample we need to create the async method, "GetTimeAsync". This will repeat the functionality of GetTime, pausing for ten seconds before returning the current time as a formatted string. The code is shown below.

private async Task<string> GetTimeAsync()
{
    await SlowTask();
    return DateTime.Now.ToString("HH:mm:ss.fff");
}

private async Task SlowTask()
{
    await Task.Run(() => Thread.Sleep(10000));
}

I've included two methods here to show different ways in which an async method can be constructed. The GetTimeAsync method returns a Task<string> object. On completion of the process, the result of the task will be a string containing the formatted time. Note that although the method is declared as returning Task<string>, the return statement, which appears in the code that will become a continuation, actually returns a string. The ten second pause is added by the awaited call to SlowTask. SlowTask returns a Task object, as it has no return value. The await keyword is used again so that the pause happens on a background thread and no return statement is required.

Try starting the program and using the Async / Await button. You should find that it behaves in the same manner as the Asynchronous Callback button. Indeed, you can click both of these buttons to run both asynchronous operations concurrently and the application will still remain responsive. This responsiveness will be lost if you click the Synchronous button.

Async Method Guidelines

There are some guidelines that should be followed when writing async methods. Firstly, async methods do not support ref or out parameters. If you would normally use such parameters to return multiple values from your method, you should instead return a Task<TResult>, where TResult is a class or structure containing properties for each of the return values, or where TResult is a Tuple.

When you return a Task or Task<TResult> from an async method, the Task must be started within the method. You should not rely on the caller to start the task. It is likely that callers will simply request the result via the await keyword. This does not run the task. If you don't start it within the method, the asynchronous operation, and the continuation code, will never be executed.

If your method is included in a library, you should only use async if the method is bound by input / output (I/O) operations. If the potentially long-running code only provides computation with no access to disks or external services, async methods are not advised. You should leave it to the caller to decide whether or not to add asynchrony or parallelism. However, you might decide to create both synchronous and asynchronous versions in order to allow the consumer of the method to choose.

21 October 2012