You're using your favorite desktop application when suddenly it
freezes. You can't interact with it and the UI won't refresh, until all
of a sudden everything goes back to normal. Sounds familiar? I'm sure
this has happened to you before. What most likely happens in such cases
is that the application performs a lengthy operation using the UI
thread, which in turn isn't available to take care of the UI until the
operation ends.
To provide a responsive UI experience, potentially lengthy operations
should be done asynchronously, on a separate thread. This isn't
anything new, but many applications still perform operations on the UI
thread. Why? One of the reasons is that asynchronous programming can be a
pain. It is far more complex, harder to write, test and maintain and if
not done properly can lead to degraded performance and even deadlocks.
Over the years, versions of .NET offered incrementally improved models
that attempted to reduce the complexity of asynchronous programming. But
it wasn't until .NET 4.5 / C# 5 that it finally happened: now we can
write asynchronous code almost as easily as we write synchronous code
and the compiler does all the heavy lifting for us. In fact,
asynchronous code can now be almost identical to its synchronous
counterpart. Without further ado, let's examine
async
and await
.Introducing async and await
Imagine that we have a Windows Forms application. When the user
clicks a button, we want to download an image from the web and display
it in the UI. The following code does that synchronously.
private void button_Click(object sender, EventArgs e)
{
WebClient client = new WebClient();
byte[] imageData = client.DownloadData("imageurl");
this.pictureBox.Image = Image.FromStream(new MemoryStream(imageData));
}
Now imagine that the internet is slow and it takes 30 seconds to
download the image. During this time the application will become
unresponsive. If the user is impatient (and rightfully so), she may
terminate the application before it even has a chance to download the
image. Clearly this isn't a great approach. We must download the image
asynchronously. Let's see how we can easily do that using
async
and await
.private async void button_Click(object sender, EventArgs e)
{
WebClient client = new WebClient();
byte[] imageData = await client.DownloadDataTaskAsync("http://imageurl");
this.pictureBox.Image = Image.FromStream(new MemoryStream(imageData));
}
This code is almost identical! This is how easy it is to write asynchronous code using
async
and await
. There are three differences in this revised code: the method is decorated with the async
keyword, the call to download the image is preceded by await
, and the method that is called to perform the actual download is DownloadDataTaskAsync
, which is the asynchronous counterpart of the DownloadData
method. Let's discuss each of these changes in more detail.
The
async
keyword decorates the method and tells the compiler that this method may contain the await
keyword. This is necessary for backward compatibility with code that was written prior to the introduction of the await
keyword in C# 5 and may use await
as an identifier. It also allows the compiler to make sure that we use await
inside the method, and emit a warning if we forget to do so.
The
DownloadData
method of the WebClient
class downloads the data synchronously before returning the control to
the caller. But we need a method that returns immediately and performs
the actual download asynchronously. Many of the .NET FCL classes now
offer asynchronous versions of their methods where applicable. As a
convention, asynchronous methods that can be awaited are named in the
form of xxxAsync. In the case of the WebClient
class however, there was already a DownloadDataAsync
method prior to .NET 4.5, so the DownloadDataTaskAsync
method was added. When you're in doubt which method to use with
'await', here is the ultimate guideline: only methods that return Task
or Task<T>
can be awaited.
The
await
keyword is probably the most interesting
change we introduced to the code. Once we start the download operation
asynchronously by calling the appropriate method, we want to release the
UI thread until the download is complete. The await
keyword does exactly that. Whenever our code encounters an await
, the method actually returns. Once the awaited operation completes, the method resumes. That is, it continues executing from where it stopped.Using await
In the example above we used
await
only once, but we can
use it multiple times in the same method. For example, we could use it
to download multiple images. This means that the method would return and
resume multiple times.
Another interesting aspect of
await
is that we can use
it almost anywhere in the code, not just as part of a simple method
call. Consider this revised button_click handler.private async void button_Click(object sender, EventArgs e)
{
WebClient client = new WebClient();
this.pictureBox.Image = Image.FromStream(
new MemoryStream(await client.DownloadDataTaskAsync("http://imageurl")));
}
Rules and Limitations
While usage of
async
and await
is very flexible, it has some limitations. Here are the most notable ones:- The
await
keyword may not be used inside acatch
orfinally
block (remember thatawait
actually causes the method to return and later resume, and it's not possible to resume back into a catch / finally) - Constructors and property accessors cannot be marked as
async
and useawait
- An
async
method cannot have aref
orout
parameters - The
await
keyword may not be used in the body of alock
statement
Exception Handling
Asynchronous programming in C# 5 was designed to be as seamless as
possible, and exception handling isn't an exception (pun intended). In
fact, we write exception handling code the same way we do in synchronous
programming, as shown in the following code example:
private async void button_Click(object sender, EventArgs e)
{
WebClient client = new WebClient();
try
{
this.pictureBox.Image = Image.FromStream(
new MemoryStream(await client.DownloadDataTaskAsync("http://someurl")));
}
catch (WebException exp)
{
// show error message
}
}
Even though the work to download the image happens on another thread,
and consequently the exception may be thrown in that other thread, it
is captured and rethrown when the control returns to the awaiting
method. This allows our code to handle the exception as if we made a
synchronous call. One interesting question is what happens with
finally
blocks. Consider the following code:WebClient client = new WebClient();
try
{
this.pictureBox.Image = Image.FromStream(
new MemoryStream(await client.DownloadDataTaskAsync("http://someurl")));
}
finally
{
// do something
}
finally
blocks are guaranteed to execute when the control leaves the corresponding try
and catch
blocks. As a result it is also guaranteed to execute if the method returns from within the try
or catch
blocks. As we already know, the await
keyword makes the method return. So what will happen in the code above? Will the finally
code execute when returning due to the await
or will it execute only after the method resumes and the code logically leaves the try
block?
Whenever you ask yourself such a question, it's useful to keep in
mind that the asynchronous support in C# 5 was designed to feel as
natural (and synchronous-like) as possible. What happens in asynchronous
code is generally what would happen in a corresponding synchronous
code. If the code above was synchronous, you would expect the
finally
block to execute after the code in the try
block completes in its entirety (assuming that no exception is thrown).
The same applies when the try block contains asynchronous code. Even
though technically the method returns as a result of the await
keyword, logically it hasn't returned yet. Hence the finally
block in the code above will execute when the code in the try
block completes (or if an exception is thrown), as it would in synchronous code.How Does it Work?
C# 5 allows us to write asynchronous code using simplified syntax,
and leave the dirty work to the compiler. Behind the scenes when an
asynchronous method is compiled, the compiler generates a state machine.
Without getting into the nitty gritty detail, here is a simplified
description of how it works.
The state machine has a method that contains the code of our original
method, but broken into multiple segments and embedded in a
switch
block. When the method executes, it inspects the state and runs the
appropriate code segment. When the method executes for the first time,
it starts with the first state, hence it starts executing from the
beginning of our original method. Recall that we can only await
methods that return Task
or Task<T>
(which derives from Task
). When we await a method, the Task
object is used to register a callback that will be invoked when the
asynchronous operation completes. When that happens, the method of the
state machine is executed again. But this time the state is different,
so the method executes the next segment of our original code, which is
the part that comes after the await
.
The is a simplified description how this works. There are more details, but that's the main idea.
Summary
C# 5 and .NET 4.5 take a huge step forward in making asynchronous
programming simple and almost seamless. With the introduction of new C#
keywords, as well as the introduction of many asynchronous versions of
methods of FCL types, asynchronous code now appears to be almost
identical to its synchronous counterpart. This also makes it relatively
easy to migrate synchronous code into asynchronous code that promotes
responsive applications and happy users...
In an upcoming tutorial I will cover some more advanced asynchronous programming features.
Comments
Post a Comment