Making Asynchronous Programming Easy

Writing applications that effectively handle long latencies can be a daunting challenge. As software applications grow more complex, more developers are building software that accesses remote resources, performs longer computations, and processes large data sets. Tasks we used to think of as being quick now may take orders of magnitude longer to complete or may never complete. Without special care taken to manage conditions that may arise from these latencies, applications can become unresponsive or unable to scale. To accommodate these conditions, C# and VB are adopting a new asynchronous programming model.

Existing asynchronous programming paradigms provide ways to work with I/O and other high-latency operations without blocking threads. However, the current patterns are often difficult to understand and can complicate the simplest operations with callbacks and custom exception handling, resulting in error-prone code or developers simply giving up. With Async, our goal now is to make asynchronous programming far more approachable so asynchronous code is as easy to write and maintain as synchronous code. Making your code asynchronous should require only simple changes and should be possible to implement in a non-disruptive manner in your code. At the same time, it should be evident when code is “going async” so that the inherently-concurrent nature of the method can be understood at a glance.

Today, we are unveiling significant language and framework enhancements in C# and Visual Basic that enable developers to harness asynchrony, letting them retain the control flow from their synchronous code while developing responsive user interfaces and scalable web applications with greater ease. This CTP delivers a lightweight asynchronous development experience as close to the standard synchronous paradigms as possible, while providing an easy on-ramp to the world of concurrency.

The Old Way

The C# and VB code snippets below show how asynchronous code is written today. I have defined a SumPageSizesAsync method that asynchronously calls a WebClient to retrieve multiple sources of data, leaving the UI responsive while multiple data requests execute against remote servers. I want to wait for all the requests to complete before calling the caller’s callback method. The code doesn’t flow naturally, with code split across two methods and a callback to handle exception cases that can arise in two places. In addition, since a foreach statement cannot span across async calls, a lambda expression calls the second method again, complicating what should be simple logic and exposing several places where mistakes could be introduced.

 public void SumPageSizesAsync(IList<Uri> uris, Action<int, Exception> callback) {
    SumPageSizesAsyncHelper(uris.GetEnumerator(), 0, callback);
}
  
private void SumPageSizesAsyncHelper(IEnumerator<Uri> enumerator, int total, 
                                     Action<int, Exception> callback) {
    try {
        if (enumerator.MoveNext()) {
            statusText.Text = string.Format("Found {0} bytes ...", total);
            var client = new WebClient();
            client.DownloadDataCompleted += (sender, e) => {
                if (e.Error != null) 
                {
                    enumerator.Dispose();
                    callback(0, e.Error);
                }
                else SumPageSizesAsyncHelper(
                    enumerator, total + e.Result.Length, callback);
            };
            client.DownloadDataAsync(enumerator.Current);
        }
        else {
            statusText.Text = string.Format("Found {0} bytes total", total);
            enumerator.Dispose();
            callback(total, null);
        }
    }
    catch (Exception ex) {
        enumerator.Dispose();
        callback(0, ex);
    }
}
 Public Sub SumPageSizesAsync(uris As IList(Of Uri), 
                             callback As Action(Of Integer, Exception))
    SumPageSizesAsyncHelper(uris.GetEnumerator(), 0, callback)
End Sub
 
Private Sub SumPageSizesAsyncHelper(
        enumerator As IEnumerator(Of Uri),
        total As Integer,
        callback As Action(Of Integer, Exception))
    Try
        If enumerator.MoveNext() Then
            statusText.Text = String.Format("Found {0} bytes ...", total)
            Dim client = New WebClient()
            AddHandler client.DownloadDataCompleted,
                Sub(sender, e)
                    If e.Error IsNot Nothing Then
                        enumerator.Dispose()
                        callback(0, e.Error)
                    Else
                        SumPageSizesAsyncHelper(
                            enumerator, total + e.Result.Length, callback)
                    End If
                End Sub
            client.DownloadDataAsync(enumerator.Current)
        Else
            statusText.Text = String.Format("Found {0} bytes total", total)
            enumerator.Dispose()
            callback(total, Nothing)
        End If
    Catch ex As Exception
        enumerator.Dispose()
        callback(0, ex)
    End Try
End Sub

The New Way

Async CTP introduces a new keyword await, which indicates that the caller would like control to return when an asynchronous method call has completed. With the await keyword in Async, the code snippet above now has less than half the code, and we’ve regained the use of simple control flow constructs such as ‘foreach’. Also, notice that the try block is no longer required – this is because any exceptions that occur now bubble up to callers naturally, just as they do in synchronous code.

 public async Task<int> SumPageSizesAsync(IList<Uri> uris) {
    int total = 0;
    foreach (var uri in uris) {
        statusText.Text = string.Format("Found {0} bytes ...", total);
        var data = await new WebClient().DownloadDataTaskAsync(uri);
        total += data.Length;
    }
    statusText.Text = string.Format("Found {0} bytes total", total);
    return total;
}
 Public Async Function SumPageSizesAsync(uris As IList(Of Uri)) As Task(Of Integer)
    Dim total As Integer = 0
    For Each uri In uris
        statusText.Text = String.Format("Found {0} bytes ...", total)
        Dim data = Await New WebClient().DownloadDataTaskAsync(uri)
        total += data.Length
    Next
    statusText.Text = String.Format("Found {0} bytes total", total)
    Return total
End Function

This gives you the best of both worlds – responsive client apps and scalable server apps enabled by asynchronous programming models, along with the straightforward coding style of traditional synchronous code.

Try It Out

Async CTP is an early preview of the new asynchronous programming pattern for C# and VB, and we’d like to hear your feedback. Download the bits, then join the conversation.

Namaste!