Visual Studio Async CTP: The C# Perspective

October 28, 2010

one comment

During today’s PDC session on the future of C# and VB, Anders Hejlsberg announced the availability of the Visual Studio Async CTP, implementing a set of changes to the C# language that support a new pattern for asynchronous programming.

There’s lots to be said about the language support and the framework implementation that enables it, and the CTP is not the final words that will be said. There’s a comprehensive set of samples you can find at the MSDN Code Gallery, and I strongly suggest that you download the CTP and run some of these samples today.

That’s what I did.

Unlike most features introduced into C# during the last few releases, this one isn’t so easy to get right. In fact, I’m willing to bet that most people will not feel immediately at home with the new syntax—the programming style it enables is not natural unless you’re programming in a functional language.

image[Source]

Continuation-Passing Style

To make you more comfortable with the key concept of continuation-passing style (CPS), Eric Lippert wrote a great series of posts culminating in today’s PDC announcement.

CPS means that instead of calling a method f, waiting for it to complete, and then proceeding with additional work g, you call f and pass to it g as a continuation—the code you want to run when f completes its original work. This reverses the way the code is written on the screen, and may be very difficult to read at first:

//Non-CPS
int m1(string s) {…}
float m2(int i) {…}
void m3(float f) {…}

//Normal form
void M() { 
    m3(m2(m1("Hello")));
}

//Explicit form
void M() {
    int i = m1("Hello");
    float f = m2(i); 
    m3(f);
}

//CPS
void m1(string s, Action<int> c1) {…}
void m2(int i, Action<float> c2) {…}
void m3(float f, Action c3) {…}

//Explicit form
void M() {
    Action c3 = () => {};//Not really necessary
    Action<float> c2 = f => m3(f, c3);
    Action<int> c1 = i => m2(i, c2); 
    m1("Hello", c1);
}

//Normal form
void M() { 
    m1("Hello", i => m2(i, f => m3(f, () => {})));
}

Async Methods

Async methods, introduced in this CTP, use CPS to specify a continuation that should be executed after an asynchronous operation completes. An async method is executed synchronously on the caller’s thread until the first await statement is encountered. An await statement indicates that you want to queue up a continuation to execute when an asynchronous task completes; the caller’s thread is then freed to do other work, and when the awaited task completes, the rest of the method (following the await statement) executes.

There’s a precise transformation from C# to framework methods that enable the above semantics; the following informal transformation might be helpful in understanding the big picture:

async void foo() {
    S;
    result = await T;
    P;
}
//imprecisely translated to:
void foo() {
    S; //synchronous, caller’s thread
       //the rest is asynchronous:
   
when T completes {
        result = T.result;
        P;
    }
}

What are the practical scenarios enabled or simplified by the introduction of this new syntax? Here’s a couple of examples; feel free to consult the “official” samples for more.

UI Responsiveness Made Easy

The simplest pattern for keeping your UI responsive and dealing as little as possible with the headaches of updating the UI from another thread is roughly the following:

  1. Identify the code X that prepares the operation with UI input; this code might have to execute on the UI thread
  2. Identify the code Y that may run in the background; typically, it takes the input from X and generates an output
  3. Identify the code Z that processes the output of Y by displaying results onto the screen; this code might have to execute on the UI thread*
  4. Implement an async method M:

    async void M() {
        X;
        var result = await TaskEx.Run(() => Y);
        dispatch Z to the UI thread if necessary;
    }

* The “awaiting” mechanism uses the synchronization context at the moment of “awaiting” to post the continuation Z; this means that in WPF/WinForms applications, where a synchronization context is present, there’s no need to worry about dispatching Z appropriately. When there’s no synchronization context, TaskScheduler.Default is used.

Here’s a trivial example of this in a console application:

async void CompareDataLength(string url1, string url2)
{
    var t1 = TaskEx.Run(
        () => new WebClient().DownloadData(url1));
    var t2 = TaskEx.Run(
        () => new WebClient().DownloadData(url2));
    var results = await TaskEx.WhenAll(t1, t2);

    //This is Z, but we don’t need to dispatch anything
    //as we can write to the console from any thread.
    if (results[0].Length >= results[1].Length)
        Console.WriteLine(url1 + " has shorter data.");
    else
        Console.WriteLine(url2 + " has shorter data.");
}
void CompareDataLengthDemo()
{
    //This is X
    Console.WriteLine("I will process input now, " +
                      "even though there’s BG work.");
    //This is Y
    CompareDataLength(
        "
http://www.google.com", "http://www.bing.com");

    //This is to demonstrate that the UI remains responsive
    string input;
    while ((input = Console.ReadLine()) != "quit")
    {
        Console.WriteLine("Say ‘quit’ if you want out.");
    }
}

“Workflow Parallel” Processing Style

If you ever used Workflow Foundation, you might have encountered the Parallel activity, an activity that appears to run code in parallel even though there’s just one thread executing the workflow. A typical use for a parallel flow is when you call a set of external components asynchronously and process their responses as they arrive.

The following method demonstrates a parallel call to multiple services. While waiting for the results, we also wait for the delay task, which simply sleeps for a certain amount of time. If this task completes, execution times out.

async void QueryServices()
{
    string[] items = { "chair", "banana", "coat" };
    var queries =
        from item in items
        let temp = item
        select TaskEx.Run(() => DoItemQuery(temp));
    Task<int> delay;
    const int TIMEOUT = 2500;
    queries = queries.Concat(new[] { delay = TaskEx.Run(
        () => { Thread.Sleep(TIMEOUT); return 0; }) });
    for (int i = 0; i < items.Length; ++i)
    {
        var completed = await TaskEx.WhenAny(queries);
        if (delay == completed)
        {
            Console.WriteLine("Timed out.");
            break;
        }
        else
        {
            int result = await completed;
            Console.WriteLine("Result: " + result);
        }
    }
}

What Else?

There’s lots more to be said about exception propagation, built-in cancellation support, progress reporting, TPL dataflow (an agents library), and other areas where the TPL simply has been extended to make working with tasks and async methods easier. Let’s leave this to a future post.

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

one comment

  1. SvickOctober 30, 2010 ב 1:46 AM

    The last code snippet is incorrect. await TaskEx.WhenAny(queries) returns *any* of the completed tasks, no matter what tasks were returned previously. So the code might write the first result multiple times (and not wait until they all finish, assuming they all finish before the timeout). To do this correctly, you have to use List> and remove completed tasks from it (or something similar). See the section “Interleaving” in the Framework part of the documentation for the CTP.

    Reply