Asynchrony in C# 5, Part One

Asynchrony in C# 5, Part One

Rate This
  • Comments 61

The designers of C# 2.0 realized that writing iterator logic was painful. So they added iterator blocks. That way the compiler could figure out how to build a state machine that could store the continuation - the “what comes next” - in state somewhere, hidden behind the scenes, so that you don’t have to write that code.

They also realized that writing little methods that make use of local variables was painful. So they added anonymous methods. That way the compiler could figure out how to hoist the locals to a closure class, so that you don't have to write that code.

The designers of C# 3.0 realized that writing code that sorts, filters, joins, groups and summarizes complex data sets was painful. So they added query comprehensions and all the rest of the LINQ features. That way the compiler could figure out how to do the right object model calls to build the query, the expression trees, and so on.

The designers of C# 4.0 realized that interoperating with modern and legacy dynamic object models was painful. So they added the dynamic type. That way the compiler could figure out how to generate the code at compile time that does the analysis in the Dynamic Language Runtime at runtime.

The designers of C# 5.0 realized that writing asynchronous code is painful, in so many ways. Asynchronous code is hard to reason about, and as we've seen, the transformation into a continuation is complex and leads to code replete with mechanisms that obscure the meaning of the code.

This shall not stand.

I am pleased to announce that there will be a C# 5.0 (*), and that in C# 5.0 you’ll be able to take this synchronous code:

void ArchiveDocuments(List<Url> urls)
{
  for(int i = 0; i < urls.Count; ++i)
    Archive(Fetch(urls[i]));
}

and, given reasonable implementations of the FetchAsync and ArchiveAsync methods, transform it into this code to achieve the goal of sharing wait times as described yesterday:

async void ArchiveDocuments(List<Url> urls)
{
  Task archive = null;
  for(int i = 0; i < urls.Count; ++i)
  {
    var document = await FetchAsync(urls[i]);
    if (archive != null)
      await archive;
    archive = ArchiveAsync(document);
  }
}

Where is the state machine code, the lambdas, the continuations, the checks to see if the task is already complete? They’re all still there. Let the compiler generate all that stuff for you, just like you let the compiler generate code for iterator blocks, closures, expression trees, query comprehensions and dynamic calls. The C# 5.0 code above is essentially a syntactic sugar for the code I presented yesterday. That's a pretty sweet sugar!

I want to be quite clear on this point: the action of the code above will be logically the same as the action of yesterday's code. Whenever a task is "awaited", the remainder of the current method is signed up as a continuation of the task, and then control immediately returns to the caller. When the task completes, the continuation is invoked and the method starts up where it was before.

If I’ve timed the posting of this article correctly then Anders is announcing this new language feature at the PDC right about… now. You can watch it here.

We are as of right now making a Community Technology Preview edition of the prototype C# 5.0 compiler available. The prototype compiler will be of prototype quality; expect features to be rough around the edges still. The idea is to give you a chance to try it and see what you think so that we can get early feedback.

I'll be posting more about this feature tomorrow and for quite some time after that; if you can't wait, or want to get your hands on the prototype, you can get lots more information and fresh tasty compiler bits from msdn.com/vstudio/async.

And of course, in keeping with our co-evolution strategy, there will be a Visual Basic 11 and it will also feature task-based asynchrony. Check out the VB team blog for details, or read all about this feature at my colleague Lucian's blog. (Lucian did much of the design and prototyping of this feature for both C# and VB; he, not I, is the expert on this so if you have deep questions, you might want to ask him, not me.)

Tomorrow: await? async? Task? what about AsyncThingy<T>? Tell me more!

(*) We are absolutely positively not announcing any dates or ship vehicles at this time, so don't even ask. Even if I knew, which I don't, and even if my knowledge had the faintest chance of being accurate, which it doesn't, I still wouldn't tell you.

  • @Iso Su:

    Anders mentioned in his talk that they were capable of transforming the code without the async keyword but they decided to include it so that it is clear a method is asynchronous (and can return before its work is done).

  • This is pretty cool.  I was just contemplating how one would make use of this feature in order to create a function which is triggered on completion of the async event.  Then I realized one could easily enough just make an async function which awaits completion of another async function and then performs whatever actions are necessary.

  • Awesome ! After yesterday's post, I expected something like that, but I had no idea how it would look like...

    I assume the FetchAsync method just starts a new Task<Document> and returns it ?

  • How do Reactive Extensions fit in this picture? Are they going to become useless, coexist, or become even more relevant with C# 5.0?

  • @Flavien, watch Bart De Smet's session on the PDC website (LINQ Take Two), he explains how Rx can take advantage of this new feature.

    But the video of Anders's session doesn't seem to be available yet, even though it was planned at the same time as Bart's...

  • @ChaosPandion

    A general syntax for monads ... like LINQ you mean?

  • Will this link in with LINQ? One of the biggest challenges in my recent WPF/LINQ to SQL applications was making LINQ calls asynchronous to avoid horrendous roundtrip times. LINQ to SQL doesn't like to be used from multiple threads.

  • So all of that talk about CPS and how it reifies and generalizes control flow so you can write whatever control flow you want... none of that is actually in C# 5? Just the ability to make stuff async?

    Don't get me wrong, I know making stuff async is a Big Friggin Deal and this is a really cool feature. I just was expecting something different after the whole CPS discussion.

    The "implementing try-catch via CPS" had me expecting the ability to implement my own control flow structures, like super-"using" that calls .Success() on normal completion and .Fail(ex) on throw before calling .Dispose() in the finally block.

    Or making it possible to change my Update function which currently reads like:

    Update(update => {

     update.DoOneThing();

     update.DoAnotherThing();

    });

    with less punctuation and avoiding the problems with definite assignment if I wanted to assign a value inside the block or return directly from it to the outer caller.

    Am I missing a way that async / await can accommodate this sort of thing, is there more yet to be announced, or is that just not part of C#5?

  • I hope this quickly finds its way into Silverlight and Windows Phone 7 to simplify all the async calls we do.

  • very cool! i cant imagine the amount of work we will have to do to reengineer our code from the EAP Model....

  • can you use await and yield in the same enumerator?

    how about try/catch ?

  • It looks like you can't use await and yield in the same enumerator. Which sucks, as this means you cannot compose async methods in the same ways you could do with normal methods.

    For example, if you wanted to pull out the "read one item ahead"-logic into a separate method (separate it from the archiving logic), you could do that by making the method an IEnumerable:

    IEnumerable<async Document> FetchDocuments(List<Url> urls)

    {

    Task<Document> previousDocument;

     foreach (Url url in urls) {

       Task<Document> thisDocument = FetchAsync(url);

       if (previousDocument != null)

         yield return (await previousDocument);

       previousDocument = thisDocument;

     }

     yield return (await previousDocument);

    }

    async void ArchiveDocuments(List<Url> urls)

    {

     foreach await (var document in FetchDocuments(urls)) {

       await ArchiveAsync(document);

     }

    }

    Of course, IEnumerable<async Document> is the existing interface IObservable<Document>. This does not seem to be possible in the current build, but I'd really like to see this feature added.

    Sure, the reactive framework solves part of this problem as a library; but I think language support for IObservable would be clearer (it's the natural combination of 'async' and 'IEnumerable'). At least I'd like to be able to use a foreach loop on IObservable (the producer side is not as important as the consumer side, as one of the most common producers will be LINQ queries and those allow hiding the complexity within the reactive framework).

  • I've looked at the 'await' support in the reactive framework now; it simply subscribes and waits until all elements were observed, then returns those as a List<T>.

    Anders' PDC demo seems to have been carefully constructed to avoid this problem. It has UI and data access intermixed, and they cannot be cleanly separated without having to fall back to 'turn yourself inside out' when you consume the incoming data stream (IObservable). Using 'await' with the observable would destroy the usability of the app, as no elements would be visible until the whole set is loaded.

  • Asynchrony. Sounds very odd. Shouldn't it be Asynchronicity?

  • As mentioned above: What about exception handling? Could I do something like this? (and what would the semantics be?)

    async void ArchiveDocuments(List<Url> urls)

    {

      try {

        Task archive = null;

        for(int i = 0; i < urls.Count; ++i)

        {

          var document = await FetchAsync(urls[i]);

          if (archive != null)

            await archive;

          archive = ArchiveAsync(document);

        }

      } catch (MyFetchException e)

      { ... }

    }

    Because ... without exception handling IO stuff is still going to be painfull (albeight a bit less than we are used to).

Page 2 of 5 (61 items) 12345