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
DumpWebPage
Async
(
string
uri
)
{
WebClient
webClient
=
new
WebClient
();
string
page
=
await
webClient
.
DownloadString
TaskAsync
(
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.
Everything after
await
is moved into a separate method.We use a new version of
DownloadString
calledDownloadStringTaskAsync
. It does the same as the original, but is asynchronous.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.
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.