I’ve been wracking my mind trying to figure out how to best summarize the new async/await feature in C# 5.
A short example is this:
async Task<Tweet[]> GetTweetsOfAllFollowers(string query, TwitterAPI api) {
var matchingTweets = new List<Tweet>();
var followers = await api.GetFollowers(); // 1
foreach(var follower in followers) {
var tweets = await api.GetRecentTweets(follower); // 2
var applicableTweets = tweets.Where(tw => tw.Text.Contains(query));
matchingTweets.AddRange(applicableTweets);
}
return matchingTweets.ToArray();
}
The interesting thing about this is how it runs. It starts out at the top, naturally, and then runs to point 1. At that point, the rest of the method, right up until the end, is packaged up as a continuation. The GetTweetsOfAllFollowers method returns and posts a call to api.GetFollowers() to the current synchronization context, meaning in most cases to the message queue on the current thread.
Right after that, the method will run and kick off its own asynchronous processing. When it’s done, it’ll pick up after point 1 and continue running through the function. Each time it encounters point 2 (which may be never, if you have no followers), it’ll do the same thing all over again.
Some pre-emptive answers:
Task<T>is a future. It’s an existing type that can carry a result, be cancelled and be faulted (and then carry a specific exception).awaitdoesn’t block; it does just the opposite. Instead of holding the thread still until the method it awaits returns, it lets the thread do other stuff —yields, you might say — until the method it awaits returns.- In some cases,
awaitcan run the method inline. Let’s sayGetFollowerscached its results; it’d return those synchronously and just carry on. - The compiler assembles the
Task<T>return value for you, while you provide the actual value that it will eventually resolve. - The compiler contorts the code for you to work with identical semantics but remain technically able to be passed as a continuation.
- You can use, throw and catch exceptions pretty much like you usually would. The compiler handles wrapping and unpacking the exception into the
Task. - An
asyncmethod isn’t flagged as such in the metadata; it merely is one of many methods to return aTask<T>. - And finally, what you all are thinking: yes, if you wrote a single-threaded program with just
async/awaitin it instead of threads or thread pools, it’d effectively work pretty much like node.js.
For more technical detail, I think I’ll link to Lucian Wischik’s technical walk. It’s the sort of thing you love because it involves potshots at competitors in PowerPoint presentations by Microsoft employees where the Microsoft employees are right, the Microsoft employees aren’t marketing and the potshot is about clumsy technical reasoning. (That’s actually just a very minor part. Most of it is gritty details and a better explanation than I can muster. I highly recommend it.)
I will also link to Eric Lippert’s deep but riveting tease-a-thon: Continuation Passing Style Revisited, parts one, two, three, four and five.
But finally, why post about this? Isn’t this just Microsoft catching up to computer science? In a way, yes. Not only could you have written this last week in Scheme, you could have written it last week in C#, assuming you’re willing to unravel the structure of your code into what the process requires. The point is that this is the first major language that I’m aware of to actually add support to unfurl your normal iterative code into callbacks using compiler support. In a world where the conventional wisdom posits that Microsoft is perpetually ripping off a Java that can’t even seem to grow some closures, I thought they deserved some credit.