Async in 4.5: Worth the Await

Brandon Bray

Developers often ask for guidance on how to write responsive user interfaces. Reading most books about the .NET Framework over the past ten years, you’ll see coverage of the asynchronous programming model which requires a lot of careful attention while coding. That’s why the async features in the latest versions of C# and Visual Basic are tremendous step forward. And language innovation like async needs a great library to bring forward the potential. In this post, Alok Shriram – a Program Manager from the .NET Base Class Library team – shows the work done in the .NET Framework to be async ready.

"Performance" is a term that is used a lot when talking about apps, but it’s actually a pretty vague term. There are at least two aspects of performance that most people think about: app launch time and throughput. Both of these can be measured and described with actual numbers. The true test of an app, however, is end-user perception. A stop-watch may tell us one thing, but a user may see something else. End-user perception is the basis of asynchrony and related features that we have built into the .NET Framework 4.5 Beta. Essentially, we can provide a more responsive UI experience if some of the more expensive app operations can be delayed. This will make the end-user happier, and it will help us make better use of computing resources than would be possible otherwise. While you’ve always been able to implement this pattern in the .NET Framework, the implementation has been difficult in practice. We’ve fixed that in the .NET Framework 4.5 Beta.

This post introduces the asynchronous features in the .NET Framework 4.5 Beta. Many of you have been introduced to “async” via the language changes in C#, but we also want to tell you about accompanying changes that we’ve made in the .NET Framework.

Asynchrony in the .NET Framework

If you have ever written any UI code, chances are that you have used the asynchronous capabilities of the .NET Framework. Asynchrony keeps the UI responsive while expensive operations are being performed. For instance, if you implement a mouse click event that has to go and fetch a large page from the Internet, and you implement it using the WebClient.DownloadString method, depending on the amount of time this call takes to return, the thread on which the UI is running will be blocked. This means that any subsequent actions performed on the UI will be delayed, and you will wind up getting a frozen UI. However, if you use an asynchronous version of the method instead (specifically, WebClient.DownloadStringAsync), the request will return control immediately, and subsequent UI interactions can be handled as they arrive, while the content is being asynchronously fetched from the resource.

In server apps, resource requests like database calls and networks calls are typically written in a synchronous manner, so a large number of threads are blocked on I/O operations. This approach can lead to a larger memory footprint for the app and poor utilization of the server hardware.

Writing code that utilizes asynchronous paradigms is useful for both client and server apps. However, despite all the benefits, asynchronous code is hard to write, debug, and maintain. The new language features introduced by the .NET Framework 4.5 make writing asynchronous code comparable to writing synchronous code.

The new async/await keywords

Before the .NET Framework 4.5 Beta, asynchronous APIs in the .NET Framework followed three patterns:

  1. The Asynchronous Programming Model (APM), which has the format BeginMethodName and EndMethodName.
  2. The Event based Asynchronous Pattern (EAP), which relies on assigning delegates to event handlers that will be invoked when an event is triggered.
  3. The Task-based Asynchronous Pattern (TAP), which relies on the Task Parallel Library (TPL) and the System.Threading.Task namespace that was introduced with the .NET Framework 4.

I won’t get into the pros and cons of the different schemes, since there are plenty of articles covering that. (A great starting point is Stephen Toub’s TAP doc.) However, to illustrate the differences in complexity, let’s consider a synchronous implementation of CopyTo( ) and the code that we would have to write in the TAP and APM models to implement a CopyToAsync() function.

Synchronous example:

public static void CopyTo(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
int numRead;
while((numRead = source.Read(buffer, 0, buffer.Length)) > 0)
{
destination.Write(buffer, 0, numRead);
}
}

APM example:

public static IAsyncResult BeginCopyTo(Stream source, Stream destination)
{
var tcs = new TaskCompletionSource();

byte[] buffer = new byte[0x1000];
Action<IAsyncResult> readWriteLoop = null;
readWriteLoop = iar =>
{
try
{
      for (bool isRead = iar == null; ; isRead = !isRead)
{
switch (isRead)
{
case true:
iar = source.BeginRead(buffer, 0, buffer.Length, readResult =>
{
if (readResult.CompletedSynchronously) return;
readWriteLoop(readResult);
}, null);
if (!iar.CompletedSynchronously) return;
break;

case false:
int numRead = source.EndRead(iar);
if (numRead == 0)
{
tcs.TrySetResult(true);
return;
}
iar = destination.BeginWrite(buffer, 0, numRead, writeResult =>
{
try
{
if (writeResult.CompletedSynchronously) return;
destination.EndWrite(writeResult);
readWriteLoop(null);
}
catch(Exception e) { tcs.TrySetException(e); }
}, null);
if (!iar.CompletedSynchronously) return;
destination.EndWrite(iar);
break;
}
}
}
}
catch(Exception e) { tcs.TrySetException(e); }
};
readWriteLoop(null);

return tcs.Task;
}

public static void EndCopyTo(IAsyncResult asyncResult)
{
((Task)asyncResult).Wait();
}

TAP example with the new C# language async support:

public static async void CopyToAsync(Stream source, Stream destination)
{
byte[] buffer = new byte[0x1000];
int numRead;
while((numRead = await source.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await destination.WriteAsync(buffer, 0, numRead);
}
}

It’s pretty clear that there is a huge difference in the complexity of the code that you have to write with the APM, as compared to either of the other two examples. You may have written asynchronous code before, but you probably didn’t find it straightforward. Ideally, the C# and Visual Basic languages (and compilers) should be doing all of the heavy lifting, and you should only need to declare your intent via a set of new keywords.

There are two new language keywords introduced in C# and Visual Basic: async and await (highlighted in yellow in the code above). There are also a significant number of new .NET Framework methods that support asynchronous apps. For example, the .NET Framework 4 Stream class has a CopyTo method. In the .NET Framework 4.5 Beta, we added a CopyToAsync method, which (not coincidentally) shares an implementation not unlike the TAP example above.

Starting with synchronous code

Most of you will likely end up using async with synchronous code, as opposed to moving code that is already asynchronous to this new pattern. So we’ll start with synchronous code, too.

This post covers the experience of developing a simple app that gets a list of movies from the Netflix OData catalog and displays the box art and movie title/summary. We will look at the initial, synchronous implementation and explain what we did to convert it to a responsive async app (it wasn’t much).

First, let’s start off by declaring a simple class called Movie to hold some details about each movie. Let’s also add a single utility method and use it to parse an HTTP response into a Movie object:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using System.Xml.Linq;

public class Movie
{
public string Title
{ get; set; }
public string BoxArtUrl
{ get; set; }
public string Summary
{ get; set; }
}

public class MainPage
{
private IEnumerable<Movie> GetMovieDataFromXDoc(XDocument doc)
{
XNamespace dRss = "http://www.w3.org/2005/Atom";
XNamespace odata = "http://schemas.microsoft.com/ado/2007/08/dataservices";

List<XElement> dox = doc.Descendants(dRss + "entry").ToList();
List<Movie> movieList = new List<Movie>();
foreach (XElement ele in dox)
{
XElement movieSummary = ele.Descendants(dRss + "summary").First();
XElement movieName = ele.Descendants(odata + "Name").First();
XElement boxArtUrl = ele.Descendants(odata + "MediumUrl").First();
Movie tempMovie = new Movie() {
BoxArtUrl = boxArtUrl.Value,
Title = movieName.Value,
Summary = movieSummary.Value };
movieList.Add(tempMovie);
}
return movieList;
}
}

Adding async support

The new async support is built on the Task Parallel Library (TPL) that was introduced in the .NET Framework 4. It uses the Task framework, and adds language support through the async and await keywords to make writing asynchronous code almost equivalent to writing synchronous code.

You use the async keyword to annotate that this method could be invoked asynchronously. You then apply the await keyword to a task inside an async method to suspend the execution of the method until that task is complete. Let’s explore these concepts by starting with some synchronous code in the MainPage class:

public List<Movie> GetMovies()
{
string query =
@"http://odata.netflix.com/Catalog/Titles?" +
@"$filter=ReleaseYear le 1989 and ReleaseYear ge 1980 " +
@"and AverageRating gt 4";
HttpClient client = new HttpClient();
client.MaxResponseContentBufferSize = Int32.MaxValue;
try
{
HttpResponseMessage resp = client.Get(new Uri(query));
string content = resp.Content.ReadAsString();
XDocument xml = XDocument.Parse(content);
List<Movie> movieList = GetMovieDataFromXDoc(xml).ToList();
return movieList;
}
catch (Do your catch logic here)
{
... and here
}
}

The code above constructs an HTTP query, sets the buffer size to a large value, and does a synchronous get to retrieve the contents of the target. It queries the Netflix OData catalog for all of the movies that were released in 1989 and that have a Netflix rating greater than four stars. We wrap it in a try/catch block to make sure we handle any exceptions, and we make a blocking call to the network with the client.Get(new Uri(query)) call. If this call were made from a UI app and the returned page was large or network latency was high or server processing time was long, then your UI would likely freeze while waiting for the query to return. We’d much prefer to make the code run the same way, but not freeze up the UI, right? To show you how to achieve that, I’ve updated the code for async, and grayed out the code that hasn’t changed (most of it).

public async Task<List<Movie>> GetMoviesAsync()
{
string query =
@"http://odata.netflix.com/Catalog/Titles?" +
@"$filter=ReleaseYear le 1989 and ReleaseYear ge 1980 " +
@"and AverageRating gt 4";
HttpClient client = new HttpClient();
client.MaxResponseContentBufferSize=Int32.MaxValue;
try
{
HttpResponseMessage resp = await client.GetAsync(new Uri(query));
string content = resp.Content.ReadAsString();
List<Movie> movieList = GetMovieDataFromXDoc(xml).ToList();
return movieList;
}
catch (Do your catch logic here)
{
... and here
}
}

As you can see, the changes that we had to make are minor. First we use the async keyword to mark our method so that we’re allowed to use the await keyword in it. We also change the return type to Task, so that callers of our method will have a handle through which to wait for our asynchronous operation’s completion. Then, we use the GetAsync method instead of the Get method on the HttpClient class. This returns a Task<HttpResponseMessage> (instead of the HttpResponseMessage that Get returns), which we await.

Finally, the pattern specifies that any method that uses an await internally should be annotated with an Async suffix, so we rename our method to GetMoviesAsync. That’s it! Now, when this method is called in the UI, it will not lock up the UI, because it will return immediately. The UI will be populated when the results are obtained. Since the UI is still interactive, you should make sure that any user interactions that might require reading or writing data (a list of movies in this example) are prepared for the case where the data is not yet populated because it is waiting on an async call.

How async works

Before we move on, it’s probably a good idea to understand what is happening in terms of code flow when we encounter an await. Let’s take a look at our asynchronous code again.

public async Task<List<Movie>> GetMoviesAsync()
{
string query = "...";
...
try
{
HttpResponseMessage resp = await client.GetAsync(new Uri(query));
string content = resp.Content.ReadAsString();
List<Movie> movieList = GetMovieDataFromXDoc(xml).ToList();
return movieList;
}
catch (Do your catch logic here)
{
... and here
}
}

In terms of control flow, what happens here is that when the compiler parses an await keyword, it wraps all the code following the await into a continuation function, which is invoked when the call to GetAsync() returns a result. This essentially means that when GetMoviesAsync() is called, control is going to flow until the await client.GetAsync(new Uri(query)) method. None of the code following this method is going to be executed until this function has returned. So, although it might appear that the GetAsync is a non-blocking call, it’s actually doing a “yield return” to the calling function. For a deeper look at the mechanics of how async works, see the resources provided at the end of this post.

Propagation of the async keyword through the call stack

Once you start using async, you’ll quickly find that it isn’t intended to be used in just a single function, but for an entire flow in your application. You’ll see that your use of await and async keywords will propagate all of the way up to the top of a logical call stack. Let’s take a look.

We defined the following async method in the earlier example:

public async Task<List<Movie>> GetMoviesAsync() {...}

This tells us that we will have to do an await on any call to this method, such as in the example below:

await viewer.GetMoviesAsync();

This essentially means that the method that contains the GetMoviesAsync() call will have to be marked as an async, which leads to the async keyword being propagated through the call stack. For a UI app, this async propagation would likely end at an event handler. When you think about it, that’s exactly what needs to happen in order to get the benefit of asynchrony. The language forces this behavior, leading you to do the right thing.

async void SearchButton_Click(object sender, RoutedEventArgs e)
{
...
await viewer.GetMoviesAsync();
...
}

This propagation is normal and just annotates the methods that are potentially awaitable. The propagation of the keywords and the return type change (on each method signature) are really the bulk of the work to migrate your code from the sync to async models. That’s pretty reasonable!

Another point to keep in mind is that the async keyword propagation will stop once you have a void return on a method. A void return async function is essentially a “fire and forget” async call. This is why, for example, you can mark an event handler delegate with the async keyword in a UI app and not worry about doing anything in the app logic that invokes the event handler.

Async APIs in .NET Framework 4.5 Beta

You are probably wondering where to first start using async in your app. An important consideration is the set of APIs that you call within your apps. First and foremost among those will be the .NET Framework APIs. In the .NET Framework 4.5 Beta, we included a significant set of new async variants of existing synchronous APIs. You will want to move to using these new APIs when you move to async for your own code.

The following is the set of async APIs that we added just for the Stream class, to give you a sense of what we’ve done in the .NET Framework to support async. You’ll find this same pattern on many other types. (You’ll be able to recognize the async APIs by the “Async” in their names.)

System.IO.Stream

  • public Task<Int32> ReadAsync(Byte[] buffer, Int32 offset, Int32 count);
  • public virtual Task<Int32> ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken);
  • public Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count);
  • public virtual Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken);
  • public Task FlushAsync();
  • public virtual Task FlushAsync(CancellationToken cancellationToken);
  • public Task CopyToAsync(Stream destination);
  • public Task CopyToAsync(Stream destination, Int32 bufferSize);
  • public virtual Task CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken);

Summary

Async is an important new feature in the .NET Framework 4.5 Beta. The new async and await keywords enable you to provide significant user experience improvements in your apps without much effort on your part. In addition, we’ve added a large set of new async APIs within the .NET Framework in the areas where they make the most sense. We see the combination of the language features and the new .NET Framework APIs as completing the async scenario, leaving the rest to you.

The Visual Studio Asynchronous Programming site on MSDN is a great resource for samples, white papers and talks on the new language features and support. You can also learn more about the Task-based Asynchronous Pattern by reading Stephen Toub’s document on the topic. You might also want to check out Asynchronous File I/O and Asynchronous Programming with Async and Await in the MSDN Library, and Asynchronous programming in the Windows Dev Center.

Have you used async? Was it as easy to migrate your synchronous code to async as this post suggested? Are there any other .NET Framework APIs that you think need async variants? Please tell us what you think.

Cheers
-Alok

0 comments

Discussion is closed.

Feedback usabilla icon