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.

  • listening to Future of c# and heard the word continuation. let's play drinking game... :D

  • Cool :) Was this influenced by Delphi Prism's async support, or is it just a coincidence? In any case, that is great syntactic sugar as async operations require a lot of ceremony and plumbing around them.

  • No more "hypothetical future versions" hooray!  

    This looks pretty amazing although you spoiled part of it with your CPS run up to PDC.  I'm going to listen to Anders finish explaining this and consider the implications.

  • Just saw it.  Its greatly simplifies Threading. cool..

  • Just the other day I was thinking about some Technical DSL to come composing Tasks easier, and now look at that. That looks neat and great...but it ain't threading, or is it? Just saw Anders' session and there seems to be no need to dispatch on the UI thread when the continuations kick in. Or was the example just well balanced to avoid that? There's still a lot for me to understand but it looks veeery neat and useful.

  • It certainly looks very, very similar to the (already shipping in Dev 10) F# async support - the big difference being the use of Task<T> rather than Async<T>.  

    I can certainly see the merits of using the existing Task<T> type, but it does raise the question of interop.  Will it become F#'s problem to adapt to match?

  • This is.... amazing. I think it might even be more beneficial than Linq.

    Linq made things we already did prettier.

    This stuff is going to help us do things we didn't even consider doing, because they were just too painful.

    And all of that for the price of just two keywords!

    Hats off.

  • Am I the only one looking at it and remembering UnrealScript docs?

    "... you can write special "state code", using all of the regular UnrealScript commands plus several special functions known as "latent functions". A latent function is a function which executes "slowly", and may return after a certain amount of "game time" has passed. This enables you to perform time-based programming – a major benefit which neither C, C++, nor Java offer. Namely, you can write code in the same way you conceptualize it; for example, you can write a script that says the equivalent of "open this door; pause 2 seconds; play this sound effect; open that door; release that monster and have it attack the player". You can do this with simple, linear code, and the Unreal engine takes care of the details of managing the time-based execution of the code."

    And now we can do the same in C# (and XNA)! What more, the UnrealScript approach was very ad-hoc with only a few magic functions, while here we have full control.

    Speaking more generally, it looks like it would make any kind of actor-based simulation really easy and straightforward to write.

  • @Frank: it wouldn't be magic if it didn't take care of dispatching continuations on the right thread for you, wouldn't it :)

  • I know that you guys aren't trying to copy F# but don't you think a more general syntax for monads would allow more flexibility?

  • This seems pretty nice and it abstracts some of the tedium of handling continuations from the programmer.  However I'm curious about further applications and how perhaps it could feed into the Reactive framework.  Tasks are great for single results, but what about series of results?  I'd love to see it tie in with IObservable<T> and provide "asynchrony" across iterators.

    For example, imagine the following:

    public async IObservable<int> CountSlowly(int start, int end, int delay)

    {

       for(int i = start; i < end; i++)

       {

           Thread.Sleep(1000);

           yield return i;

       }

    }

    Then there is the whole concept of synchronization.  The "await" keyword makes simple linear continuations nice and easy, but what about waiting across multiple sets of tasks grouped into levels of priority?  I was a huge fan of the Cw MSR project and the "Polyphonic C#" project that inherited from and I thought that the language semantics introduced there were of the both elegant method I've ever seen for complex synchronization scenarios.  The Reactive framework does introduce joins over observables which provides the functionality but, as Anders put it, you have to twist you coding inside-out with lambdas to utilize.

    So I think that the new enhancements are great and work well with the existing .NET 4.0 TPL concepts, but I hope that the language designers also look at Reactive and see the huge amount of potential that could be realized in also marrying those concepts.  I can only imagine that by the time that C# 5.0 is anywhere near shipping that Reactive will be similarly ready for prime time as a part of .NET Framework 5.0.

  • How related async, await with Rx? Are they both solving the same async problems?

  • Isn't the term "ship vehicle" redundant? I don't think I've ever seen a ship that wasn't also a vehicle. Can a ship be anything other than a boat or rocket? Hopefully C# 5 will be of the latter variety.

  • @guest According to Eric, you can 'await' any type with a GetAwaiter() method (or extension method) matching the required pattern.  Today's Rx drop includes an Observable.GetAwaiter() method, so you should be able to 'await' any IObservable<T>.  However, you can only *declare* 'async' methods which return void, Task, or Task<T>.  In terms of interoperability potential, I think this is about the best we could have hoped for.

  • I wonder decision was made to require async keyword for a method? Compiler slices methods with yield returns in the similar manner without any additional keywords.

Page 1 of 5 (61 items) 12345