Home > Uncategorized > C# 5.0 async/await

C# 5.0 async/await

First reaction: hurrah! This is the right language feature at the right time, and a perfect application of the power of compilers. I’ve been using JavaScript for the last few months and constantly wishing for a feature like this, but at the same time thinking how it could be done better in C#.

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.

Inside an async method, 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).

Inside an 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 task.

Advertisements
Categories: Uncategorized Tags: , ,
  1. Omer Mor
    October 30, 2010 at 4:38 pm

    the ‘await’ keyword works not only for tasks, but for everything you can call GetAwaiter() on (including extension methods).
    I don’t think you suggestions extends easily to the broader pattern here.

    • earwicker
      October 30, 2010 at 5:40 pm

      I’m suggesting that the trigger for the new feature should be a type incompatibility, which can be resolved by the compiler. It can do that by looking for GetAwaiter(), even though I’ve simplified it here by talking about Task. Shouldn’t make any difference, really.

      • Omer Mor
        October 30, 2010 at 9:49 pm

        This could break horribly.
        Imagine someone adds an extension method to GetAwaiter on System.Object.
        Suddenly your implicit “awaits” will be executed on every non-void method call, in every async function.

  2. October 30, 2010 at 7:39 pm

    I think the C# team’s choice of opting into the new behaviour (with explicit “await”) is the right approach. Without requiring a new keyword, you’d suddenly be introducing new behaviour for any existing code that happens to return an object with a GetAwaiter method (as unlikely as that might be). (The compiler can hardly detect type incompatibility if you’ve used “var” on the left-hand side.)

    In an example of coroutines I posted today (http://code.logos.com/blog/2010/10/coroutines_with_c_5s_await.html), “await SwitchToOther” seems like clearly the right syntax. Of, if you use “await” to move execution of a method to a different thread (see “SwitchTo” in http://download.microsoft.com/download/5/B/9/5B924336-AA5D-4903-95A0-56C6336E32C9/TAP.docx), explicitly specifying “await” feels much more natural.

    Even if await were only used with Task, explicitly specifying the keyword makes reasoning about the code far easier when reading it; the possible suspend/resume points are clearly identified. And as Omer said, explicit “await” is better for the broader pattern.

    • earwicker
      October 30, 2010 at 8:13 pm

      I’m not saying there should be no new keywords: the async modifier on the method is essential to avoid changing the meaning of existing code. Once that is put on a method, it should mean that the normal semantics of method calls should extend across the boundaries of return types that have a GetAwaiter method on them.

      I also already addressed the point about var – the default assumption should be normal semantics. So if you assign something that returns a GetAwaiter to a var, then the type of the var should be inferred to be the type ultimately produced by the operation, by default, as that is what you want in the majority of cases. If this is tricky for the compiler to do under the present implementation, then I submit that as a criticism of the implementation! 🙂

      I’m wondering what it will be like when – if this feature is really put to good, widespread use – our code is going to be peppered with a mess of awaits that really don’t need to be there, and won’t add value in terms of comprehension. They’ll just be noise that makes the code harder to understand. Your list-merging example is great, but I don’t see how the presence or absence of the await prefix particularly affects the readability of it. A different method name might help more?

      From the point of view of the caller, when you call a method to get a result, that should be implied by the normal imperative programming syntax of the language. It’s the abnormal case – where you explicitly want to interfere with the calling mechanism – that should require new syntax, if any.

      Edit: I keep saying async when I mean await.

  3. October 30, 2010 at 8:54 pm

    I had forgotten/overlooked that you said the new behaviour would only be applied in methods already decorated with “async”; as you rightly point out, no existing code will be changed.

    As I understand, you’re saying that in the code
    var x = expr;
    if expr returns an object with a GetAwaiter method, and the object returned by GetAwaiter has BeginAwait and EndAwait methods, the inferred type of “x” will be the type of “expr.GetAwaiter().EndAwait()” (and the compiler will perform its standard await-rewriting logic)?

    In the cases where EndAwait() has no return type (void method), should “await expr;” still be required, or should “expr;” alone be sufficient to invoke the C# compiler’s await-rewriting logic?

  4. Omer Mor
    October 30, 2010 at 9:52 pm

    Initially I wrote this as a reply to your reply, but it’s more general so I re-post:
    Your suggestion for implicit “awaits” could break horribly.
    Imagine someone adds an extension GetAwaiter() method to on System.Object.
    Suddenly your implicit “awaits” will be executed on every non-void method call, in every async function.

    • earwicker
      October 31, 2010 at 12:01 am

      Absolutely – but to echo my earlier response to Bradley, that is a problem with the technical implementation, not the idea of seamlessly extending the semantics of imperative/sequential coding style. The latter is the thing I’m concerned with – I don’t know if it can be achieved through a slight tweak to the CTP approach (maybe it can’t).

      The use of method names is perhaps too loose a requirement, and thus forces an explicit keyword – and so it’s the wrong implementation for what I’m suggesting.

      One solution might be to require Task or Task<T> as the return type. Or better, to have a new attribute that must be on whatever type is being returned, and Task would be just one example of a class having that attribute.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: