Async CTP: developer stories


We’ve shipped the Async CTP! And VB also has iterators!

I’m writing this blog post at 8.30pm on the evening before the Async CTP will ship. We’re all pretty nervous and excited. Most of the Async/VB/C# team are at an “MVP Insiders” party. I’m sitting here fretting over code and writing tutorials.

Excited, and writing tutorials

I’ve been working on concurrency for my entire professional life, since graduating from college in 1995. And despite this background I have to say that the ideas in the Async CTP took me quite by surprise. The ideas came from F# Async Workflows and from the Axum prototype released by Microsoft last year, and we’ve developed them further to make them fit fluently into C# and VB.

Because the ideas are fairly new, there hasn’t been much academic literature on the subject, nor textbooks, nor training courses. We’re having to invent new concepts and patterns, and then figure out the most effective way to teach those concepts.

One of those surprising concepts is that asynchrony doesn’t mean “background thread”. Most people think it does! It’s a little cocky for us to come out and tell the world “you’ve been using the word ‘async’ wrongly all these years”, but that’s what we’re doing.

Async Function DoWork() As Task
    ‘ … CPU-bound work to compute fast fourier transforms
End Function

 

When people look at this code, about 80% will see the word Async and immediately assume that it’s going to run that work on a background thread. Those are the approximate results of our initial usability studies and internal testing. Of course, that’s not what it actually means. What it means is that the method might have Await expressions in it, and (if it does) then it might end up returning its resultant Task before the body of the method has actually completed. I’ll spell out our motives for co-opting the word “async” in a future post.

Another surprising concept is that of single-threaded concurrency. It shouldn’t have been so surprising, since people have been doing cooperative multitasking for decades — including in Windows 3.1 with the Yield keyword, and before that CallCC in Scheme and ML, and always with co-routines. But I think that the Await keyword makes cooperative multitasking more valuable now than it has been in the past. I’ll be writing more about why we like this approach (as compared to the “zillions of threads” approach of the Go language, for instance), in a future post.

Nervous, and fretting over code

We’ve spent the last week building an installer, testing it, signing it, and preparing release material.

Today (the day before we ship the CTP) we discovered a serious bug where VB’s XML-literals didn’t work in async or iterator methods, so I fixed the bug, hastily tested it, we scrapped the installer and started rebuilding it over from scratch. That process takes several hours and involves the cooperation and good will of many kind people in the company. The Async CTP touches about 5% of the .cpp and .h files that make up the C# and VB compilers. I wrote much of the CTP, and so feel personally responsible for every bug.

Now (the evening before we ship the CTP) I just discovered another serious bug where VB will crash if a variable’s type is inferred from an await expression and then you double-dot off it, e.g. “Dim result = Await t : result.Descendants.ToString()”. I’ve fixed the bug, and I have to figure out whether to drag people away from their party, start a rebuild of the installer, and jeopardize the timing of the release in case it doesn’t finish in time.

I think it’s not quite that serious: everyone can have their party and their sleep, and the fix can be left for an update in a few days’ time or maybe skipped entirely.

This Async CTP has been largely an “underground” project by a small group of 5 program-managers. Microsoft divides its employees into three categories: developers who write code, testers who test it, and program managers who look after specifications and deadlines. For most of the CTP’s development cycle, we five PMs had to fill all three roles to build something substantial enough that management would approve it. This meant many late nights — on some weeks sleeping more nights at the office than at home. What made all this effort worthwhile was the joy of working with such smart colleagues, and the satisfaction of finally being able to implement, refine and publish the answer that those smart colleagues delivered to the research question I first started asking in 1995: “how should you think about concurrency and make it usable for everyday programming?

 

Comments (4)

  1. Kevin Gadd says:

    I'm really pleased to see you guys building on the Task stuff you added (which, honestly, was of no use to me) by adding these abstractions for co-operative multitasking, because I've been using it to build applications for a couple years now and I had to do it manually using iterator functions and a custom scheduler. I can't wait to be able to ditch my scheduler and iterators and move to using await/async 🙂

    Out of curiosity, how do you handle exception propagation for await statements? Doing it using traditional iterators is very difficult because there is no way to 'raise' an exception into an iterator when you resume it, so your only choice is to Dispose it. This also means you can't wrap a yield in a try/catch, only a try/finally. Did you guys do some magic here to fix those problems, or do the same caveats apply? If you fixed these problems with iterator-based concurrency I will be buying whatever version of VS comes with C#5 on day one 🙂

  2. We fixed these problems! …

    (1) What you're describing is the current iterator-based approach to async methods (e.g. Jeff Richter's async iterators). In these, rather than having "await" expressions, you will yield-return the awaitee. Then there's some driver method who iterators over the method, picks up each yield-returned awaitee, and calls MoveNext when the result has come. This "TOP-DOWN" driver is why exceptions are hard.

    Instead what we've done with await is BOTTOM-UP driving. The awaited task itself calls MoveNext when it's done.

    (2) You talk about the difficulty of getting exceptions at the right point. When the user writes "await e", the compiler ends up generating three calls:

     $temp = e.GetAwaiter();

     $temp.BeginAwait(AddressOf MoveNext);

     …. (suspend the method now. when the task is done, we'll resume with the following)

     $temp.EndAwait();

    If the task had any exceptions, then they get propagated correctly by EndAwait.

    (3) C# iterators don't let you put a yield inside a try/catch. That was due to an implementation detail of how C# happened to transform iterators into state-machines. Await uses an entirely new transformation, one without these limitations, and so you ARE able to put await inside try/catch. Incidentally, VB will use the new transformation for its iterators too and will allow Yield inside a Try/Catch, albeit at the cost of a couple of extra IL instructions.

  3. Kevin Gadd says:

    That's great to hear! I had assumed you simply built this on top of the iterator infrastructure, but this bottom-up approach to driving tasks seems like it's more suitable to solving these issues. This actually puts you guys ahead of the iterator-based approaches, even when compared with the superior iterator implementation in Python that twisted and imvu.task use to implement yield-based cooperative threading.

    A couple other details that come to mind:

    Are the GetAwaiter, BeginAwait and EndAwait methods part of a publicly exposed interface? It would be great to have them exposed in the same manner as IEnumerable so that it's possible for library authors to create 'awaitable' data types that aren't Task, so that people can make a partial transition to using Task and await/async without having to overhaul all their code. In my case, I already have a bunch of code and data types that I could probably transition to this model if those methods were exposed in interfaces.

    One of the big gripes I had with using iterator functions to implement cooperative threading was that the C# compiler would not allow iterator functions to have a return type other than IEnumerable/IEnumerator. This made it hard to enforce true type safety and correctness in my scheduling code, because it was possible for someone to try and schedule a List<object> or something stupid like that. It would have been great if I had been able to do something like:

    interface IMyTask : IEnumerable<TaskObject>

    or

    abstract class MyTask : IEnumerable<TaskObject>

    And then write my iterator functions like:

    MyTask IteratorFunction () { yield return … }

    instead of having to make them IEnumerable.

    So, based on this – is it possible for us to do something similar when using async/await, or are we locked to using the Task type directly for these purposes? It would be great if we could build on top of the types and interfaces you've created to make our code more expressive and let the compiler do more verification for us, as long as it doesn't complicate the compiler too significantly.

    Oh, and finally, as a more pipe-dreamy sort of question – Does this feature tie into the Code Contracts that have recently been added into C#? It would be wonderful if contracts were extended to allow more complex assertions about the behavior of asynchronous code – for example, 'does not access any global state' would be an extremely useful compile-time assertion to be able to make about an async function, because functions that satisfy it can be freely scheduled on any thread as long as you ensure their arguments are not shared between threads.

    I'd love to chat with you in depth about these new features since I've personally shipped some real-world applications based on this cooperative threading model and I'd be glad to share some of my test cases and some of the issues we've run into, in the hopes that they can all be addressed for the initial release of this feature instead of having to be addressed in a service pack. Feel free to drop me an email if you'd like to chat!

  4. TimeBender says:

    Can we use this in a WinForms App with a Grid and update progress or cancel  as it Loads Data (10-20 K rows) from SQL Server?

    An e.g. to show this would be nice!!!