Chapter 1. Introduction

Let’s start with a high-level introduction to the async feature in C# 5.0, and what it means for you.

Asynchronous Programming

Code is asynchronous if it starts some long-running operation, but then doesn’t wait while it’s happening. In this way, it is the opposite of blocking code, which sits there, doing nothing, during an operation.

These long-running operations include:

  • Network requests

  • Disk accesses

  • Delays for a length of time

The distinction is all about the thread that’s running the code. In all widely used programming languages, your code runs inside an operating system thread. If that thread continues to do other things while the long-running operation is happening, your code is asynchronous. If the thread is still in your code, but isn’t doing any work, it is blocked, and you’ve written blocking code.

Note

Of course, there is a third strategy for waiting for long-running operations, called polling, where you repeatedly ask whether the job is complete. While it has its place for very short operations, it’s usually a bad idea.

You’ve probably used asynchronous code before in your work. If you’ve ever started a new thread, or used the ThreadPool, that was asynchronous programming, because the thread you did it on is free to continue with other things. If you’ve ever made a web page that a user can access another web page from, that was asynchronous, because there’s no thread on the web server waiting for the user’s input. That may seem completely obvious, but think about writing a console app that requests the user’s input using Console.ReadLine(), and you might be able to imagine an alternative blocking design for the web. It may have been a terrible design, yes, but it would have been possible.

The difficulty with asynchronous code is that, quite often, you want to know when an operation is finished. Then you want to do something else. This is trivially easy to do in blocking code: you can just write another line of code below the long-running call. In the asynchronous world, however, this doesn’t work, because your next line will almost certainly run before the asynchronous operation has finished.

To solve this, we have invented a menagerie of patterns to run some code after a background operation completes:

  • Inserting the code into the background operation, after the main body of the operation

  • Signing up to an event that fires on completion

  • Passing a delegate or lambda to execute after completion (a callback)

If that next operation needs to execute on a particular thread (for example, a WinForms or WPF UI thread), you also need to deal with queuing the operation on that thread. It’s all very messy.

What’s So Great About Asynchronous Code?

Asynchronous code frees up the thread it was started on. That’s really good for lots of reasons. For one thing, threads take up resources on your machine, and using fewer resources is always good. Often, there’s only one thread that’s able to do a certain job, like the UI thread, and if you don’t release it quickly, your app becomes unresponsive. We’ll talk more about these reasons in the next chapter.

The biggest reason that I’m excited about async is the opportunity it provides to take advantage of parallel computing. Async makes it reasonable to structure your program in new ways, with much finer-grain parallelism, without the code becoming complicated and unmaintainable. Chapter 10 will explore this possibility.

What Is Async?

In version 5.0 of the C# language, the compiler team at Microsoft has added a powerful new feature.

It comes in the form of two new keywords:

  • async

  • await

It also relies on some additions and changes to the .NET Framework 4.5 that power it and make it useful.

Note

Async is a feature of the C# compiler that couldn’t have been implemented by a library. It performs a transformation on your source code, in much the same way that lambdas and iterators do in earlier versions of C#.

The feature makes asynchronous programming a lot easier by eliminating the need for complex patterns that were necessary in previous versions of C#. With it, we can reasonably write entire programs in an asynchronous style.

Throughout the book, I’m going to use the term asynchronous to refer to the general style of programming that is made easier by the C# feature called async. Asynchronous programming has always been possible in C#, but it involved a lot of manual work from the programmer.

What Async Does

The async feature is a way to express what to do after a long-running operation is completed, one that’s easy to read but behaves asynchronously.

An async method is transformed by the compiler to make asynchronous code look very similar to its blocking equivalent. Here is a simple blocking method that downloads a web page.

private void DumpWebPage(string uri)
{
    WebClient webClient = new WebClient();
    string page = webClient.DownloadString(uri);
    Console.WriteLine(page);
}

And here is the equivalent method using async.

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

They look remarkably similar. But under the hood, they are very different.

The method is marked async. This is required for any methods that use the await keyword. We’ve also added the suffix Async to the name of the method, to follow convention.

The interesting bit is the await keyword. When the compiler sees this, it chops the method up. Exactly what it does is pretty complicated, so for now I will introduce a false construct that I find useful as a way to think about simple cases.

  1. Everything after await is moved into a separate method.

  2. We use a new version of DownloadString called DownloadStringTaskAsync. It does the same as the original, but is asynchronous.

  3. That means we can give it the new second method, which it will call when it finishes. We do this using some magic that I’ll tell you about later.

  4. When the download is done, it will call us back with the downloaded string—which we can use, in this case, to write to the console.

private void DumpWebPageAsync(string uri)
{
    WebClient webClient = new WebClient();
    webClient.DownloadStringTaskAsync(uri) <- magic(SecondHalf);
}

private void SecondHalf(string awaitedResult)
{
    string page = awaitedResult;
    Console.WriteLine(page);
}

What happens to the calling thread when it runs this code? When it reaches the call to DownloadStringTaskAsync, the download gets started. But not in this thread. In this thread, we reach the end of the method and return. What the thread does next is up to our caller. If it is a UI thread, it will go back to processing user actions. Otherwise, its resources might be released. That means we’ve written asynchronous code!

Async Doesn’t Solve Everything

The async feature has deliberately been designed to look as similar to blocking code as possible. We can deal with long-running or remote operations almost as if they were local and fast, but keep the performance benefits of calling them asynchronously.

However, it’s not designed to let you forget that there are background operations and callbacks happening. You need to be careful with lots of things that behave differently when you use async, including:

  • Exceptions and try..catch...finally blocks

  • Return values of methods

  • Threads and context

  • Performance

Without understanding what’s really happening, your program will fail in surprising ways, and you won’t understand the error messages or the debugger to be able to fix it.

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.