C# 5.0 async/await
Second reaction: why would you want to have that
await keyword in there?
In a method, the normal behaviour is sequential, imperative, etc. In a block of code, the semi-colons are like joining operators that change statements together into a list. The semi-colons mean: “and when that’s done, do this”.
In the same way, nested expressions mean “get the value of this thing, and feed it into the outer expression”, so they also imply an ordering.
If you look at Eric Lippert’s blog, there’s a huge debate over what would be a better keyword than
await. No one can quite come up with the zinger. There’s a reason for this! They’re trying to think of a word that means “do what you’d normally do.” And the right word for that is: no word at all.
Task<T> should be special. If you’re going to extend an abstraction (imperative code) to cover a new case, you should go for it whole-heartedly, or you’re just going to confuse people more.
A method that returns
int, when called, just returns an int. It doesn’t have to be told explicitly: “By the way, don’t come back until you have an answer for me”. That’s the default behaviour for a method call (or any other expression evaluation).
async method, this default should be extended to
Task<T>. The evaluation of a
Task<T> should be a
T, by default.
var document = FetchDocumentAsync();
As this code appears in an
async, each place where it evaluates to a
Task, the compiler should assume you want a
T, because you didn’t tell it any different. That’s what the
async marker means: you want imperative semantics to extend across tasks.
If you do want a
Task, that’s when you should specify a keyword:
var task = start FetchDocumentAsync();
Notice how easy it was to think of a suitable keyword? This is no coincidence!
When you’re thinking about how this stuff works under the covers, the
start keyword means “switch off the clever handling for the following expression”. But mostly you shouldn’t be thinking like that. That’s the whole point of an abstraction. It frees you to think at the higher level. Just call or evaluate things: don’t worry about how they’re implemented. You’re getting the same semantics anyway, as a caller: your code is imperative. It seems to wait, as if it was a thread making blocking calls, and that’s what matters.
So, I think this is a great feature, but the choice of default is the wrong way round.
Update (Oct 30, 2010): – I’ve radically simplified how this would be implemented (but then added some more complexity regarding delegates). See this programmers.stackexchange answer for more details. I’ll keep the stuff below so that the feedback I received can relate to it.
One interesting result of this suggestion is it leaves open the question of how to explicitly await a task when you’ve used
start to obtain one. It’s not a problem for
Task<T> where you will naturally try to convert it to a
T, and that is where the implicit await will happen. It’s more for the case of
Task where it doesn’t return a value.
So how about treating a
Task as syntactically like a zero-argument delegate? To await it, you just put
() after it!
var warning = start ShowWarning(); // and later: warning();
PS. I also think
async should be