Chapter 4. Writing Async Methods

Now we know how great asynchronous code is, but how hard it is to write? It’s time to look at the C# 5.0 async feature. As we saw previously in What Async Does, a method marked async is allowed to contain the await keyword.

private async void DumpWebPageAsync(string uri)
{
    WebClient webClient = new WebClient();
    string page = await webClient.DownloadStringTaskAsync(uri);
    Console.WriteLine(page);
}

The await expression in this example transforms the method, so it pauses during the download, then resumes when the download is done. This transformation makes the method asynchronous. In this chapter, we’ll explore writing async methods like this one.

Converting the Favicon Example to Async

We’ll now modify the favicon browser example from earlier to make use of async. If you can, open the original version of the example (the default branch) and try to convert it by adding async and await keywords before reading any further.

The important method is AddAFavicon, which downloads the icon, then adds it to the UI. We want to make this method asynchronous, so that the UI thread is free to respond to user actions during the download. The first step is to add the async keyword to the method. It appears in the method signature in the same way that the static keyword does.

Then, we need to wait for the download using the await keyword. In terms of C# syntax, await acts as a unary operator, like the ! not operator, or the (type) cast operator. It is placed to the left of an expression and means to wait for that expression asynchronously.

Finally, the call to DownloadData must be changed to instead call the asynchronous version, DownloadDataTaskAsync.

Note

An async method isn’t automatically asynchronous. Async methods just make it easier to consume other asynchronous methods. They start running synchronously, until they call an asynchronous method and await it. When they do so, they necessarily become asynchronous themselves. Sometimes, an async method never awaits anything, in which case it runs synchronously.

private async void AddAFavicon(string domain)
{
    WebClient webClient = new WebClient();
    byte[] bytes = await webClient.DownloadDataTaskAsync("http://" + domain + "/favicon.ico");
    Image imageControl = MakeImageControl(bytes);
    m_WrapPanel.Children.Add(imageControl);
}

Compare this to the other two versions of this code we’ve looked at. It looks much more like the original synchronous version of the code. There’s no extra method, just a little extra code in the same structure. However, it behaves much more like the asynchronous version that we wrote in Converting the Example to Use Manual Asynchronous Code.

Task and await

Let’s break down the await expression we’ve written. Here is the signature of the WebClient.DownloadStringTaskAsync method:

Task<string> DownloadStringTaskAsync(string address)

The return type is Task<string>. As I said in An Introduction to Task, a Task represents an ongoing operation, and its subclass Task<T> represents an operation that will have a result of type T at some point in the future. You can think of Task<T> as a promise of a T when the long-running operation completes.

Task and Task<T> can both represent asynchronous operations, and both have the ability to call back your code when the operation is done. To use that ability manually, you use their ContinueWith methods to pass a delegate containing the code to execute when the long-running operation is done. await uses the same ability to execute the rest of your async method in the same way.

If you apply await to a Task<T>, it becomes an await expression, and the whole expression has type T. That means you can assign the result of awaiting to a variable and use it in the rest of the method, as we’ve seen in the examples. However, when you await a non-generic Task, it becomes an await statement, and can’t be assigned to anything, just like a call to a void method. This makes sense, as a Task doesn’t promise a result value, it only represents the operation itself.

await smtpClient.SendMailAsync(mailMessage);

There is nothing stopping us from splitting up the await expression, so we can access the Task directly, or do something else, before awaiting it.

Task<string> myTask = webClient.DownloadStringTaskAsync(uri);
// Do something here
string page = await myTask;

It is important to fully understand the implications of this. The method DownloadStringTaskAsync is executed on the first line. It begins executing synchronously, in the current thread, and once it has started the download, it returns a Task<string>, still in the current thread. It’s only later when we await that Task<string> that the compiler does something special. This is all still true if you write the await on the same line as the call to the asynchronous method.

The long-running operation starts as soon as the call to DownloadStringTaskAsync is made, which gives us a very simple way to perform multiple asynchronous operations concurrently. We can just start multiple operations, keeping all the Tasks, then await them all afterwards.

Task<string> firstTask = webClient1.DownloadStringTaskAsync("http://oreilly.com");
Task<string> secondTask = webClient2.DownloadStringTaskAsync("http://simple-talk.com");
string firstPage = await firstTask;
string secondPage = await secondTask;

Note

This is a dangerous way to await multiple Tasks, if they may throw exceptions. If both operations throw an exception, the first await will propagate its exception, which means secondTask is never awaited. Its exception will not be observed, and depending on .NET version and settings, may be lost or even rethrown on an unexpected thread, terminating the process. We’ll see better ways to do this in Chapter 7.

Async Method Return Types

There are three return types that a method marked async may have:

  • void

  • Task

  • Task<T> for some type T

No other return type is allowed because async methods in general aren’t finished when they return. Typically, an async method will await a long-running operation, which means that the method returns quickly, but will resume in the future. That means no sensible result value is available when the method returns. The result will be available later.

Note

I’ll make the distinction between the return type of a method—for example, Task<string>—and the result type that the programmer actually intends to give to the caller, which in this case is string. In normal non-async methods, the return type and the result type are always the same, but the difference is important for async methods.

It’s obvious that void is a reasonable choice of return type in an asynchronous situation. A async void method is a “fire and forget” asynchronous operation. The caller can never wait for any result, and can’t know when the operation completes or whether it was successful. You should use void when you know that no caller will ever need to know when the operation is finished or whether it succeeded. In practice, this means that void is used very rarely. The most common use of async void methods is in the boundary between async code and other code, for example a UI event handler must return void.

Async methods that return Task allow the caller to wait for the operation to finish, and propagate any exception that happened during the asynchronous operation. When no result value is needed, an async Task method is better than an async void method because it allows the caller to also use await to wait for it, making ordering and exception handling easier.

Finally, async methods that return Task<T>, for example Task<string>, are used when the asynchronous operation has a result value.

Async, Method Signatures, and Interfaces

The async keyword appears in the declaration of a method, just like the public or static keywords do. Despite that, async is not part of the signature of the method, in terms of overriding other methods, implementing interfaces, or being called.

The only effect that the async keyword has is on the compilation of the method to which it is applied, unlike the other keywords that are applied to a method, which change how it interacts with the outside world. Because of this, the rules around overriding methods and implementing interfaces completely ignore the async keyword.

class BaseClass
{
    public virtual async Task<int> AlexsMethod()
    {
        ...
    }
}

class SubClass : BaseClass
{
    // This overrides AlexsMethod above
    public override Task<int> AlexsMethod()
    {
        ...
    }
}

Interfaces can’t use async in a method declaration, simply because there is no need. If an interface requires that a method returns Task, the implementation may choose to use async, but whether it does or not is a choice for the implementing method. The interface doesn’t need to specify whether to use async or not.

The return Statement in Async Methods

The return statement has different behavior in an async method. Remember that in a normal non-async method, use of the return statement depends on the return type of the method:

void methods

return statements must just be return;, and are optional

Methods that return a type T

return must have an expression of type T (for example return 5+x;) and must exist at the end of the method on all code paths

In a method marked async, the rules apply in different situations:

void methods and methods that return Task

return statements must just be return; and are optional

Methods that return Task<T>

return must have an expression of type T and must exist at the end of the method on all code paths

In async methods, the return type of the method is different from the type of the expression found in the return statement. The compiler transformation can be thought to wrap up the value you return in a Task<T> before giving it to the caller. Of course, in reality, the Task<T> is created immediately, and only filled with your result value later, once any long-running operation is done.

Async Methods Are Contagious

As we’ve seen, the best way to consume a Task returned by an asynchronous API is to await it in an async method. When you do this, your method will typically return Task as well. To get the benefit of the asynchronous style, the code that calls your method must not block waiting for your Task to complete, and so your caller will probably also await you.

Here’s an example of a helper method I’ve written that gets the number of characters on a web page, and returns them asynchronously.

private async Task<int> GetPageSizeAsync(string url)
{
    WebClient webClient = new WebClient();
    string page = await webClient.DownloadStringTaskAsync(url);
    return page.Length;
}

To use it, I need to write another async method, which returns its result asynchronously as well:

private async Task<string> FindLargestWebPage(string[] urls)
{
    string largest = null;
    int largestSize = 0;
    foreach (string url in urls)
    {
        int size = await GetPageSizeAsync(url);

        if (size > largestSize)
        {
            size = largestSize;
            largest = url;
        }
    }

    return largest;
}

In this way, we end up writing chains of async methods, each awaiting the next. Async is a contagious programming model, and it can easily pervade a whole codebase. But I think that because async methods are so easy to write, this isn’t a problem at all.

Async Anonymous Delegates and Lambdas

Ordinary named methods can be async, and the two forms of anonymous methods can equally be async. The syntax is very much like normal methods. Here is how to make an asynchronous anonymous delegate:

Func<Task<int>> getNumberAsync = async delegate { return 3; };

And here is an async lambda:

Func<Task<string>> getWordAsync = async () => "hello";

All the same rules apply in these as in ordinary async methods. You can use them to keep code concise, and to capture closures, in exactly the same way you would in non-async code.

Get Async in C# 5.0 now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.