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
Anonymous
October 28, 2010
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 :)Anonymous
October 28, 2010
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.Anonymous
October 28, 2010
The comment has been removedAnonymous
November 01, 2010
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!!!