Chưa phân loại

Asynchronous programming in C# using async and await

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 usingasync 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 andawait. There are three differences in this revised code: the method is decorated with the asynckeyword, 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 theDownloadData 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 theawait 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 a catch or finally block (remember that await 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 use await
  • An async method cannot have a ref or out parameters
  • The await keyword may not be used in the body of a lock 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 andcatch blocks. As a result it is also guaranteed to execute if the method returns from within the tryor 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 thetry 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.

 

Happy coding!!

 

From tech.pro

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s