85

Could someone please be kind enough to confirm if I have understood the Async await keyword correctly? (Using version 3 of the CTP)

Thus far I have worked out that inserting the await keyword prior to a method call essentially does 2 things, A. It creates an immediate return and B. It creates a "continuation" that is invoked upon the completion of the async method invocation. In any case the continuation is the remainder of the code block for the method.

So what I am wondering is, are these two bits of code technically equivalent, and if so, does this basically mean that the await keyword is identical to creating a ContinueWith Lambda (Ie: it's basically a compiler shortcut for one)? If not, what are the differences?

bool Success =
    await new POP3Connector(
        "mail.server.com", txtUsername.Text, txtPassword.Text).Connect();
// At this point the method will return and following code will
// only be invoked when the operation is complete(?)
MessageBox.Show(Success ? "Logged In" : "Wrong password");

VS

(new POP3Connector(
    "mail.server.com", txtUsername.Text, txtPassword.Text ).Connect())
.ContinueWith((success) =>
    MessageBox.Show(success.Result ? "Logged In" : "Wrong password"));
David Klempfner
  • 8,700
  • 20
  • 73
  • 153
Maxim Gershkovich
  • 45,951
  • 44
  • 147
  • 243

2 Answers2

86

The general idea is correct - the remainder of the method is made into a continuation of sorts.

The "fast path" blog post has details on how the async/await compiler transformation works.

Differences, off the top of my head:

The await keyword also makes use of a "scheduling context" concept. The scheduling context is SynchronizationContext.Current if it exists, falling back on TaskScheduler.Current. The continuation is then run on the scheduling context. So a closer approximation would be to pass TaskScheduler.FromCurrentSynchronizationContext into ContinueWith, falling back on TaskScheduler.Current if necessary.

The actual async/await implementation is based on pattern matching; it uses an "awaitable" pattern that allows other things besides tasks to be awaited. Some examples are the WinRT asynchronous APIs, some special methods such as Yield, Rx observables, and special socket awaitables that don't hit the GC as hard. Tasks are powerful, but they're not the only awaitables.

One more minor nitpicky difference comes to mind: if the awaitable is already completed, then the async method does not actually return at that point; it continues synchronously. So it's kind of like passing TaskContinuationOptions.ExecuteSynchronously, but without the stack-related problems.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 2
    very well said - I try to defer to Jon's posts as they're so much more extensive than anything I would have time to put into an answer on SO, but Stephen's absolutely right. WRT what's awaitable (and GetAwaiter in particular), his post #3 is very helpful IMHO :) http://msmvps.com/blogs/jon_skeet/archive/2011/05/13/eduasync-part-3-the-shape-of-the-async-method-awaitable-boundary.aspx – James Manning Jan 07 '12 at 16:06
  • 5
    Stephen's spot on here. For simple examples it's easy to think that async/await is just a shortcut for ContinueWith - however, I like to think of it in the reverse. Async/await is actually a more powerful expression of what you used to use ContinueWith for. The issue is that ContinueWith(...) uses lambdas, and allows execution to get transferred to the continuation, but other control flow concepts such as loops are pretty impossible if you have to put half of the loop body before the ContinueWith(...) and the other half after. You end up with manual continuation chaining. – Theo Yaung Jan 07 '12 at 21:19
  • 8
    Another example where async/await is much more expressive than ContinueWith(...) is flowing of exceptions. You can await multiple times within the same try block, and for each stage of execution, their exceptions can be funneled to the same catch(...) block without having to write tons of code that do that explicitly. – Theo Yaung Jan 07 '12 at 21:21
  • 4
    The last portion of async/await that's notable is that it's a "higher level concept" whereas ContinueWith(...) is more manual and explicitly has lambdas, delegate creations, etc. With higher level concepts there's more opportunity for optimization - so for example, multiple awaits in the same method actually "share" the same lambda closure (it's like the overhead of a single lambda), whereas ContinueWith(...) gets the overhead each time you invoke it, because you explicitly wrote a lambda, so the compiler gives that to you. – Theo Yaung Jan 07 '12 at 21:23
  • Could you elaborate on what would happen if that `Connect` method threw an exception in the OPs code? Would `ContinueWith` still be fired in that case? And if so, is there a nicer approach to get _that_ behavior using only `async`/`await`? I want to run two tasks one after the other, and I don't care if the first one throws an exception, the second should always run. AFAICT, if I went with awaits, I'd have to explicitly handle exceptions and create the aggregate exception. – julealgon Jul 03 '15 at 16:11
  • If `Connect` threw a `SpecificException`, then the `ContinueWith` would execute; accessing the `Result` property would raise an `AggregateException` wrapping the `SpecificException`. That exception would then be ignored. `I want to run two tasks one after the other, and I don't care if the first one throws an exception, the second should always run.` There's a pattern for that: `try { await first(); } finally { await second(); }` which works for C#6 / VS2015, but on VS2013 you'd have to `try { await first(); } catch { } await second();`. – Stephen Cleary Jul 04 '15 at 12:49
  • Update: The use of SynchronizationContext.Current was changed in ASP.NET Core. So if you are reading this with ASP.NET Core in mind, see https://blog.stephencleary.com/2017/03/aspnetcore-synchronization-context.html – Moby Disk May 08 '20 at 16:27
  • 1
    @MobyDisk: To clarify, `await` still captures `SynchronizationContext.Current` just like it always has. But on ASP.NET Core, `SynchronizationContext.Current` is `null`. – Stephen Cleary May 08 '20 at 16:37
9

It's "essentially" that, but the generated code does strictly more than just that. For lots more detail on the code generated, I'd highly recommend Jon Skeet's Eduasync series:

http://codeblog.jonskeet.uk/category/eduasync/

In particular, post #7 gets into what gets generated (as of CTP 2) and why, so probably a great fit for what you're looking for at the moment:

http://codeblog.jonskeet.uk/2011/05/20/eduasync-part-7-generated-code-from-a-simple-async-method/

EDIT: I think it's likely to be more detail than what you're looking for from the question, but if you're wondering what things look like when you have multiple awaits in the method, that's covered in post #9 :)

http://codeblog.jonskeet.uk/2011/05/30/eduasync-part-9-generated-code-for-multiple-awaits/

Gordon Leigh
  • 1,263
  • 2
  • 11
  • 23
James Manning
  • 13,429
  • 2
  • 40
  • 64