In his usual quiet and unpretentious way, Anders Hejlsberg stepped up on stage at Microsoft PDC yesterday – and proceeded to launch a revolution. Two new keywords added to the C# and VB.Net languages was all it took, but the impact of those changes will be felt by programmers for years to come. And not just programmers: unlike other changes made to the .Net languages across the versions, these new additions will produce tangible benefits for users.
The Status Quo
Anders started his presentation by noting a current trend in applications [watch]: they are becoming increasingly connected. We’ve had client-server applications for decades, but we’re now seeing more and more applications that have to call out to a number of different services and co-ordinate their responses. This brings a number of problems. First there’s the latency - the application having to wait as the requests and responses pass back and forth over the network. Then, if we’re not careful, the application appears to hang, unable to repaint the user-interface because it’s too busy waiting for the server to respond. Meanwhile, on the other side of the network, all the server’s threads are tied up, waiting for responses to come back from other servers, so it stops responding to incoming requests – and scalability plummets.
Now we all know what the answer is: asynchronous programming. Rather than tying up our main thread waiting for a response to come back, launch the request on another thread and set up some kind of event handler so that you can be notified when the response comes back, and then finish the work.
We have the answer, but it’s not a pretty one, as anybody who has programmed a serious client-server application in Silverlight will tell you. In Silverlight there is no way of calling the server synchronously: Silverlight forces you to call asynchronously so that the browser hosting your application can remain responsive. In fact, there’s no way of doing something as simple as showing a dialog synchronously: if the way your application continues is dependant on the users’ response, you have to move the continuation logic into an event handler attached to the dialog so that it can be executed once the user clicks their choice of button.
Inside-out Thinking
Programming like this requires you to turn your thinking inside out, because you have to prepare your response (in an event-handler or whatever) before you even make the request. And if in your response you’re going to make another asynchronous call, you have to prepare your response for that even before your first response. Anders gave a small example of that [watch]. Here’s a small method that queries the NetFlix web service. Nice straight-forward code:
Make that asynchronous and see what happens:
See how it’s turned inside out: in line 4 we prepare our what-happens-next by attaching a lambda to the DownloadStringCompleted event, and the call to download the string is the very last thing we do. Then notice that we can no longer return a value from the method: it isn’t available when the method returns. Instead we have to get the user to pass in an Action that we can call back when the result is ready. So we’re forced into changing all the places where we call the QueryMovies method too: the effects of this change ripple out through the code. As Anders said, “it becomes lambdas all the way down”.
No wonder we only do this stuff when absolutely necessary – and our customers pay a price, getting applications that become unresponsive when busy, and servers that are not as efficient as they could be. All because, for mere mortals at least, asynchronous programming is just too hard.
Not any longer.
Asynchronous Programming for the rest of us
Up on stage, Anders introduced his two new keywords, async and await [watch]. Here’s how the asynchronous version of QueryMovies looks in C# 5.0:
It’s identical to our nice straight-forward but sadly synchronous version, with the exception of four small changes (highlighed in orange). The first is the addition of the async keyword in front of the method definition. This tells the compiler that it’s going to have to work some magic on this method and make it return its result asynchronously. This produces the second change: the method now returns a Task<Movie[]> instead of just Movie[] – but notice how the return statement is unaltered – the compiler is taking care of turning the return value into a Task.
In case you’re wondering, Task is a class that was introduced in .Net 4.0 as part of the Task Parallel Library. It simply encapsulates a value that is going to become available at some point in the future (hence, why in some languages the equivalent concept is called a Future). Importantly, Task provides the ContinueWith that allows you to schedule an event handler to be called when that value is available.
The most significant change is the introduction of the await keyword in line 4, and the change to call DownloadStringTaskAsync. DownloadStringTaskAsync is just a version of DownloadString that, instead of returning string, returns Task<string>. Again, this Task<string> is something that will deliver a string sometime in the future. Adding the await keyword in front of the method call allows us to await that future moment, at which point the string will be popped out of its Task wrapper and the method will continue as normal.
But get this: it is not waiting by blocking the current thread. Under the covers the compiler takes everything in the method that comes after the await keyword, everything that depends on the value we’re waiting for, and packages it up into a lambda function – an event handler. This lambda function it attaches to the Task<string> returned by DownloadStringTaskAsync so that it can be run when the string becomes available.
There are very few restrictions on what the bodies of async methods can look like. All the usual control flow operations, if statements, foreach, while, etc. work. Best of all, exception handling works without needing any changes. The compiler takes care of all the twistedness of the asynchronicity, leaving you to think in straight lines.
All this means that, once C# 5.0 is here, there will be no excuses for applications that suffer white-outs while they do some work. And it means some cheap improvements to scalability on the server side. Anders showed an example of a server application that processes some RSS feeds over which he waved the async wand [watch]. The time needed to process the page dropped from about 1.25 seconds to 0.25 seconds – 5 times faster. Here’s a snippet of the code so you can see again how few changes are needed:
This is big news. There are other languages that can already do this kind of thing, F# being one of them. But I don’t know of any other mainstream language that does it, or does it as neatly as this. Anders and team have created an incredibly powerful feature, but exposed it a way that everybody will be able to use.
You can play with this now. Anders announced a Visual Studio Async CTP which contains new versions of the C# and VB.Net compilers, and the necessary supporting APIs in the framework (including Async versions of lots of the existing APIs). It installs on top of Visual Studio 2010, and I gather that it enables Asynchronous methods in both .Net 4.0 and Silverlight.
Further Reading
- Whitepaper: Asynchrony in .Net: an excellent overview of the new features being added in the languages and framework.
- Eric Lippert’s blog: Eric is a member of the C# compiler team, and he’s running a series of posts on the design and implementation of the async feature.
- Jon Skeet’s blog: Jon has been digging into various details of the async implementation, and (as if Anders hasn’t already given us enough!) also speculating about further additions to C# 5.0 to supplement async support.
- Ian Griffiths has his own summary of the async functionality which digs into the exception handling side of it.
- C# Language Specification for Asynchronous Functions
- The C# team blog has an interesting Silverlight 4 Facebook API example.
5 comments:
Just a minor niptick – the rest of the function after the “await” (the continuation) doesn't have to run on the same thread. That depends on the current SynchronizationContext. So if you start the method from an UI thread in WPF or WinForms, the continuation will run on the same (UI) thread, but it's not true in general.
of topic, pls do not use url shortners, they hide real links. nice post btw.
Mike,
Thanks for the feedback. Actually, it was the first time I've tried using Url Shortners in the post - I was trying to get a handle on which links people were finding useful.
Nice post. Thanks for sharing.
svick brings up a great point. If the compiler simply converts the code after an 'await' to a lambda and tacks it onto the task, it's up to the task to determine which thread the subsequent code runs on. If no cross-thread marshaling is performed in any of the tasks or lambda callbacks, it will all run on the thread completing the innermost task. Speaking of which, it looks as though the async keyword is meaningless without the use of the await keyword, unless the compiler is injecting some cross-thread marshaling for invocation of the async method, which would make me a bit uncomfortable (as it would if it were doing so for the callbacks/tasks). That would tie the compiler into the thread pool and/or message pump implementations. So, assuming a clean implementation without implicit cross-thread marshaling, it would seem that in order to make use of this mechanism, one would need to call methods (API or custom) that are coded the traditional way and return tasks created without compiler help. But correct me if I'm wrong.
These new keywords do seem like they'll be useful and will help... RAD-type developers to write better code without stressing their brains too much. I am just a little concerned that there may be confusion over which thread winds up running what code. Also the server code snippet you're showing leaves me with more questions than answers so I'll have to go over and see the whole example. I'm used to service calls being synchronous so this model would seem to be incompatible, and I have no idea how making the operation async would reduce the overall processing time, aside from avoiding the thread saturation issue, potentially, in an asynchronous service model.
Thanks for the info. :)
Post a Comment