Freigeben über


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!

Comments

  • Anonymous
    October 28, 2010
    This may seem like a small thing, but shouldn't you be disposing the WebClient when you're done with it? Or does "await" and/or DownloadDataTaskAsync somehow manage that for you? If so, how?

  • Anonymous
    October 28, 2010
    It looks like you announced async programming for F# a year ago, blogs.msdn.com/.../f-in-vs2010.aspx Anyway, the F# guys and Microsoft Research deserve a lot of credit for this: the C# feature does look like a copy of the F# feature, and I'm sure they'll work well together. It would be nice to see that mentioned.

  • Anonymous
    October 28, 2010
    Does this work in any version of silverlight? If so, which version did it first appear? I've been using the old async approach recently. I still have not had any success uploading stuff from the client and getting a response from the server whether the upload was successful or not.

  • Anonymous
    October 28, 2010
    Ok, so now it seems we have Rx Extensions, PLINQ, and now this.  Seems like an awful lot to choose from.  A blog post on when and how to leverage each of these would be extremely helpful.   I'm betting that there'll be a ton of overlap in their usage. I'm kind of feeling another PRISM vs MEF debacle coming on.  Sure, they do some things differently and provide some functionality that the other does not.  But there's a ton of functionality overlap.

  • Anonymous
    October 29, 2010
    I guess what people need to understand is that these are features that will be added into the language (and the .NET Framework) itself. Yes, these features will work for Silverlight. F# or C# - these are products of the same company. There are many features in VB.NET that have been picked up from C# - no one makes a big deal out of it...

  • Anonymous
    October 29, 2010
    The comment has been removed

  • Anonymous
    October 29, 2010
    The comment has been removed

  • Anonymous
    October 29, 2010
    The comment has been removed

  • Anonymous
    October 30, 2010
    @Sam I agree MVC usage would be interesting, I read somewhere it wasn't near 5% yet. I assume if MVC was building a strong corporate base Microsoft would be stating it, the quick introduction of the Razor view engine IMHO is a sign they totally missed the mark. Our company recently halted all R and D with Silverlight, MVC and EDM because the prototypes with MVC were nearing a million lines of code, all of our future development is PHP and MySql based. Productivity was Microsoft's biggest benefit over open source, the unique part is with Microsoft's current trend they are closing the gap on their own.

  • Anonymous
    October 30, 2010
    @Sam I agree MVC usage would be interesting, I read somewhere it wasn't near 5% yet. I assume if MVC was building a strong corporate base Microsoft would be stating it, the quick introduction of the Razor view engine IMHO is a sign they totally missed the mark. Our company recently halted all R and D with Silverlight, MVC and EDM because the prototypes with MVC were nearing a million lines of code, all of our future development is PHP and MySql based. Productivity was Microsoft's biggest benefit over open source, the unique part is with Microsoft's current trend they are closing the gap on their own.

  • Anonymous
    November 01, 2010
    How does this relates with .net 4.0 Parallel processing? For instance, for this particular case what would be the advantages of using the await instead of a Parallel.ForEach? Also, In the parallel case I can cancel parallel tasks and move on without having to wait for all of them to finish - suppose that I want to cancel the wait if a method call returns -1. Can I do that with await?

  • Anonymous
    November 02, 2010
    This is a major advance. F# has shown the way. I might eventually even get the idea to be a standard feature across all the languages I use from time to time.  (Don't hold your breath though!)  I was putting a stub of code into PHP yesterday (to talk to a .NET "web service") and this might have changed my decision about the design!!

  • Anonymous
    November 10, 2010
    Hi All, I wanted to make sure everyone is aware of the Async CTP Forum we've set up on MSDN: social.msdn.microsoft.com/.../threads Please join us there to discuss further questions that you may have on the Async CTP! Thanks, Lisa Feigenbaum Lisa (dot) Feigenbaum (at) microsoft (dot) com Visual Studio Community Program Manager

  • Anonymous
    January 10, 2011
    Wow -- Async code that is actually readable by a human. :0) Great work.