Initial thoughts on C# 5’s async support

As many of you will have seen yesterday, the major new feature of C# 5 was announced yesterday: support for asynchronous operations with new contextual keywords of "async" and "await". I haven’t had a lot of time to play with them yet, but I have a few initial thoughts. Note that these won’t make any sense to you unless you’ve also watched the talk Anders gave, or read Eric’s blog post, or something similar. Links are at the bottom of this post rather than peppered throughout.

Asynchronous != parallel

Do you remember when LINQ was first announced, and there was a huge fear that it was effectively putting SQL into C#, when in fact the truth was more general? Well, it’s the same here. The language feature allows a method to execute asynchronously, but it won’t create new threads and the like automatically.

I suspect there’s going to be a lot of confusion about which parts of the asynchronous model are provided by the language and which by the framework. Obviously I’ll hope to clarify things where I can, but it’s important to understand that the language isn’t going to automatically start extra threads or anything like that.

In the Channel9 PDC interview, Anders stressed this point pretty hard – which suggests he thinks it’s going to cause confusion too. I became a lot more comfortable with what’s going on after reading the draft spec – which is part of the CTP.

Language and framework interaction

It looks like the language changes have been designed to be pattern-based, a little like LINQ and foreach. The async modifier is present more for the sake of developers than the compiler, and the await contextual keyword simply requires a GetAwaiter() method to be available (which can be an extension method); that method has to return something for which BeginAwait(Action) and EndAwait() are valid – and again, these can be extension methods.

One of the tests I performed yesterday was to try to use this myself, with a custom "awaiter factory" – it worked fine, and it’s definitely an interesting way of exploring what’s going on. Expect a blog post along those lines some time next week.

So far, so framework-neutral… the only slight fly in the ointment is the reliance on Task and Task<T>. Just as an iterator block with yield return 10; would usually be declared to return IEnumerable<int>, so an async method ending in return 10; would have a return type of Task<int>. I don’t think that’s actually too bad – but I’ll have to dig into exactly what the language relies on to be completely comfortable with. Are there any aspects of tasks which are usually configured by their factories, for example? If so, what does the compiler do?

My gut feeling is that the language is still keeping the framework at an appropriate distance as far as it can, but that it’s got to have something like Task<T> as a way of representing an asynchronous operation.

But is it any use?

Again, I’m really going on gut feelings so far. I think it’s going to be very useful – and I wish I had something similar in Java when writing internal services at work. I like the fact that it’s relatively simple – it feels much simpler than dynamic typing, for example – so I think I’ll be able to get my head round it.

It’s important to note that it’s not a free lunch, and doesn’t try to be. It removes much of the error-prone mechanical drudgery of writing asynchronous code, but it doesn’t attempt to magically parallelize everything you do. You still need to think about what makes sense to do asynchronously, and how to introduce parallelism where appropriate. That’s a really good thing, in my view: it’s about managing complexity rather than hiding it.

Conclusion

I think C# 5 is going to be a lot of fun… but I also think you’re going to need to understand the Task Parallel Library to really make the most of it. Keep the two separate in your head, and understand how they complement each other. If ever there was a good time to learn TPL thoroughly, it’s now. Learning the C# 5 features themselves won’t take long. Mastering the combination of the two in a practical way could be a much bigger task.

Links

Obviously there are lots of resources flooding out of Microsoft (and other bloggers) at the moment. Here are a few to start with:

  • The Visual Studio async home – probably the best site to bookmark, as I’m sure it’ll get updated over time. Download the CTP from here, as well as watching various videos.
  • PDC 2010 player – as this was announced at PDC, there are lots of talks there, including the session where Anders introduces the feature.
  • Eric Lippert’s first blog post on async – with more to come, of course. Follow the Async or C# 5.0 tags.
  • A blog post by Michael Crump with a CTP walkthrough

22 thoughts on “Initial thoughts on C# 5’s async support”

  1. On Twitter I think you mentioned not being convinced by the exception model, as far as I’m aware, this exception design matches that of the tasks lib which has been available for some time, are you planning to blog about that concern at all?

  2. Unfortunately the async feature smells half-baked to be. Take the example on Michael Crump’s blog, and try to separate data access and UI in LoadMoviesAsync().

    You’ll find yourself wanting an asynchronous stream of items (IObservable), but there’s no language support for it – neither for producing, nor for consuming.
    So the async keyword stops being useful as soon as there’s a list of items and you don’t want to wait for all items before you start processing the first one.
    If “async” is so much like “yield”, why not allow mixing both, allowing to write an IObservable
    as easily as an IEnumerable?
    Why no “foreach await (var element in observable)”?
    Especially the lack of the latter seems troublesome to me, apparently you’re supposed to do “foreach (var element in await observable)” which waits for ALL elements before processing the first one; or (if you don’t want that) you have to avoid async/await and do it the old-fashioned way and write your continuation manually when you subscribe to the IObservable.

  3. Hi Jon,

    > Are there any aspects of tasks which are usually configured by their factories, for example? If so, what does the compiler do?

    Since async methods return a Task, I guess you can just create the Task object yourself if you need to. Of course, if you do that, you lose most of the benefit of the new feature…

    BTW, it looks like the last sentence of your first paragraph is incomplete

  4. @meandmycode: I wasn’t convinced by the example Anders gave – not the exception model itself. My issue was that Anders showed an example where the code within the method would be causing an exception, rather than the code which was being awaited. But yes, I’m sure I’ll blog about it.

    @Daniel: These are valid concerns, I’m sure. I think I’ll need to play with it all, find appropriate patterns etc. Hopefully we’re early enough in the cycle that well-thought-out feedback can still make a difference.

  5. @DanielGrunwald Anders mentioned on the Channel 9 PDC talk that the Reactive guys have an IAsyncEnumerable which will allow you to do what you want.

  6. @Thomas: While the async operation you call will probably create a Task, *another* one will be created for your async method – and you can’t control that.

    I’ve fixed the first paragraph, thanks :)

  7. I would also watch Bart’s talk, which gives a better overview of the LINQ space as a whole.

  8. @Daniel – Watching Anders talk it didn’t strike me as half baked – looked well structured and useful to me.

    Anders demoed that you could compose tasks together – so you could do WaitAny, WaitAll on groups of tasks – and you could also do “AndThen” on individual tasks (including composites) – I think “AndThen” would give you pretty easy foreach type functionality, plus would make the foreach itself asynchronous.

    One of the final slides that Anders talked through had a number of download tasks, as soon as they arrived each download was processed in someway, and then the finally there was a WaitAll where all the processed objects were combined together.

  9. @Daniel Grunwald:
    > Unfortunately the async feature smells half-baked to be.

    I agree, but for a completely different reason. It seems to lack the generality that could be there.

    Eric Lippert’s recent series of blog posts on continuation passing style has a clear timing, because that’s what “await” is doing: converting the remainder of the method into a continuation and then passing it to Task.ContinueWith. As Eric says (Part 4[1]):

    > If you can write a compiler that turns your favourite
    > language into CPS, and you have an orchestrator that
    > knows how to dispatch the next continuation without
    > consuming stack, then as we’ve seen, you have the
    > all the building blocks you need to add any control
    > flow that you like to that language as library calls.

    So why is this async/await approach in C# disappointing? Because when I look at F# Computation Expressions, which the F# library uses to provide both seq and async blocks (respectively C# iterators and async) it also allows me to create my own, with some flexibility. E.g. to create a Reader monand in F# one defines a computational expression with the right set of members (or rather, one largely copies if from an existing implementation :-) [2]).

    The F# compiler does the hard work of converting a series of expressions into a series of continuations, and your implementation defines how these execute.

    Anders’ example:

    async Task ProcessFeedAsync(string url) {
    var text = await DownloadFeedAsync(url);
    var doc = ParseFeedIntoDoc(text);
    await SaveDocAsync(doc);
    ProcessLog.WriteEntry(url);
    }

    becomes in F# (today, no need for a CTP) something close to (I really should check this):

    let ProcessFeedAsync (url: string ) =
    let worker = async {
    let! text = DownloadFeedAsync(url);
    let doc = ParseFeedIntoDoc(text);
    do! SaveDocAsync(doc);
    ProcessLog.WriteEntry(url);
    }
    Async.Start worker

    Where each let! and do! is just like the new await in C#. But in this case it is the “async” that defines their meaning. In a different kind of computation expression seq block they would have a different meaning.

    [1] http://blogs.msdn.com/b/ericlippert/archive/2010/10/26/continuation-passing-style-revisited-part-four-turning-yourself-inside-out.aspx

    [2] http://weblogs.asp.net/podwysocki/archive/2010/01/18/much-ado-about-monads-creating-extended-builders.aspx

  10. @Richard: I understand the frustration here, but I think it *may* be that each language is doing the appropriate thing for its target audience.

    Don’t forget that it’s not just alpha geeks who use C# (whereas I suspect it *is* just alpha geeks who use F# – which isn’t a criticism, btw). I suspect that adding more generality would also make the language harder to understand for most developers – *and* easier to abuse.

    Maybe I’m wrong – and part of me would love to see the F# generalised computation expressions in C# – but I think it’s at least worth considering.

  11. @Stuart
    > Watching Anders talk it didn’t strike me as half baked – looked well structured and useful to me.
    That’s because he structured the example in his presentation to avoid the problematic case.
    Yes you can compose tasks, but in the case I’m describing, you don’t have any tasks, just something producing elements asynchronously (‘IEnumerable‘; e.g. represented by IObservable).
    Writing a foreach on top of that is possible but non-trivial.

    Well it’s not much of a problem, as you’ll be able to extract out the foreach logic into an async method and then call it with an (async) lambda. So there probably will be an extension method:
    ForEach(this IObservable observable, Func loopBody);

    But still, I think that if IEnumerable/foreach/yield are baked into the language, and async support is baked in, then the async version of IEnumerable (IObservable/foreach await/async yield) should be in as well. It would make async orthogonal to the existing language features.

  12. Pedantic terminology alert: async generally *does* means concurrency; what it doesn’t necessarily mean is parallelism. Concurrency means you have more than one operation in flight; but those operations may be paused. Parallelism means that at least some of your concurrent operations are making progress simultaneously.

    Parallelism implies concurrency. Concurrency doesn’t necessarily imply parallelism. Async is a means of handling concurrency. Async thus does not necessarily imply parallelism, but it does imply concurrency for reasonable definitions of concurrency, because the language feature splits code into chunks that are restartable at different positions – i.e. paused, but still in flight.

  13. @Barry: While I’m not entirely sure I agree, I’ll change this to parallelism just to make it more clearcut :)

  14. To clarify, is this specifically a C# 5 feature? I’d imagine the final result will be incorporated into C# 5, but it sounded to me like this will be an optional addition to the language before the C# 5 time frame. In particular I don’t remember seeing “C# 5″ mentioned anywhere in the official posts or Anders’ talk about this.

    Minor point aside, I’m loving this pattern for Silverlight UI development. It greatly simplifies asynchronous calls and I’m looking forward to the final product.

  15. @Dave: Eric post specifically said they’re not talking about dates or a ship vehicle yet. That said, it has been identified as a C# 5 feature in a number of places.

    Of course, it’s possible that this is the *only* C# 5 feature, but with more features following in C# 6 fairly soon afterwards.

  16. One are where I think the asynchrony abstraction could be improved is in handling cancellation. I’m not a fan of having to pass cancellation tokens through to all async methods – the rest of the model hides the mechanism so well, that it seems a shame to have to expose this bit of it. I wonder if something like how TransactionScope and ambient transactions work could be applied to cancellation. Under the covers, it fine to use cancellation tokens – it would just be nice to be able to hide that when fine grained cancellation control isn’t required.

  17. Something that’s not made clear by Anders’ talk is the fact that this works *exactly* like the handling of IEnumerable: ‘await’ is equivalent to ‘foreach’, and ‘async’ methods are equivalent to ‘yield’ing methods.

    ‘foreach': duck-typed calls to .GetEnumerator() returning a thing it can .MoveNext() and get .Current.

    ‘await': duck-typed calls to .GetAwaiter() returning a thing it can .BeginAwait(Action) and .EndAwait().

    ‘yield’ing methods: generate an implementation of IEnumerable that uses a state machine to jump back into a method where it left off when .MoveNext() is called.

    ‘async': generate an implementation of Task that uses a state machine to jump back into a method when .BeginAwait() calls it’s action.

    Things to note about this:
    * ‘async’, like ‘yield’ is meant to make implementing easy 95% of the time, enough that it’s worth making APIs using this interface.
    * ‘await’, like ‘foreach’ is meant to make *using* easy 95% of the time, so that you can read *what* the method is doing, not *how*.
    * ‘Task’ is not actually required, but almost always will (should?) be used.

    You can still implement your crazy CPS stuff, just it doesn’t “poison” all your users with an ugly API.

  18. @Simon: While Task isn’t required for the awaiter, it *is* required by the async method signature. An async method will *always* return a Task or Task. That’s the only bit which isn’t compile-time-duck-typed.

  19. I’ve always imagined a language advancement where you simply code everything as atomic tasks with inputs and outputs, and then you would specify which tasks are dependent on others. The compiler/tools would verify that the dependencies aren’t circular, i.e. it forms a lattice, and then at runtime the framework decides based on the number of available cores what the best way to traverse that lattice most efficiently would be while honoring all of the dependencies.

    The problem is currently we often program things in serial that don’t really need to be serial, such as maybe loading several configuration files that don’t depend on each other. We could make this a parallel operation, but why bother with all the overhead of dealing with threads if it’s only going to net us a performance gain that only shortens the load time ever so slightly. So many times we opt not to make many optimizations because we have much more important work to do than making something run slightly faster. The fact that we might code one thing to happen after the other is often arbitrary and enforces an order that isn’t required. We really need a way to be able to say “hey, do this task A, and also do this task B, and when both are done, do task C” This way it is clear that task A and B could potentially be run in parallel if there is an extra core available.

Comments are closed.