异步等待关键字和ContinueWith Lambda表达式是否相同?

85

请问有没有好心人能够确认我对 Async Await 关键字的理解是否正确?(使用 CTP 版本 3)

到目前为止,我已经明白在方法调用之前插入 await 关键字实际上做了两件事情:A. 它创建了一个立即返回和 B. 它创建了一个“续体”,该续体在异步方法调用完成后被调用。无论如何,该续体都是该方法代码块的剩余部分。

所以我的问题是,这两段代码是否在技术上等效,如果是,这是否意味着 await 关键字与创建一个 ContinueWith Lambda 相同(即:它基本上是一个编译器的快捷方式)?如果不是,它们有什么区别?

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");

Visual Studio

(new POP3Connector(
    "mail.server.com", txtUsername.Text, txtPassword.Text ).Connect())
.ContinueWith((success) =>
    MessageBox.Show(success.Result ? "Logged In" : "Wrong password"));
2个回答

86

总的想法是正确的 - 方法剩余部分被转换为某种 continuation(继续)。

“快速路径”博客文章详细介绍了async/await编译器转换的工作原理。

以下是我能想到的区别:

await关键字还利用了“调度上下文”概念。如果存在,则调度上下文为SynchronizationContext.Current,否则为TaskScheduler.Current。然后在调度上下文中运行 continuation(继续)。因此更接近的近似值将是将TaskScheduler.FromCurrentSynchronizationContext传递给ContinueWith,如果必要,可以回退到TaskScheduler.Current

实际的async/await实现基于模式匹配;它使用可等待的模式,允许等待除任务以外的其他内容。一些例子包括 WinRT 异步 API、一些特殊方法如Yield、Rx observables 以及不会对 GC 产生太大压力的特殊 socket 可等待对象。任务很强大,但它们不是唯一可等待的对象。

我想到了一个更微不足道的区别:如果可等待对象已经完成,则async方法在那一点上实际上不会返回;它会同步地继续执行。因此它有点像传递TaskContinuationOptions.ExecuteSynchronously,但没有堆栈相关的问题。


2
说得非常好 - 我尝试遵循Jon的帖子,因为它们比我在SO上回答问题所能花费的时间更加详细,但Stephen绝对是正确的。关于什么是可等待的(特别是GetAwaiter),他的第三篇文章在我看来非常有帮助 :) http://msmvps.com/blogs/jon_skeet/archive/2011/05/13/eduasync-part-3-the-shape-of-the-async-method-awaitable-boundary.aspx - James Manning
5
Stephen在这里说得很准确。对于简单的例子来说,人们可能认为async/await只是ContinueWith的快捷方式,但我更倾向于反过来思考。实际上,async/await是一种比以前使用的ContinueWith更强大的表达方式。问题在于ContinueWith(...)使用lambda表达式,并允许执行被传递到继续执行的地方,但如果必须在ContinueWith(...)之前放置循环体的一半,在之后放置循环体的另一半等控制流概念,则诸如循环等其他控制流概念就会变得非常困难。你最终会得到手动继续链接。 - Theo Yaung
8
异步/等待比ContinueWith(...)更加表达力的另一个例子是异常的流动。您可以在同一个try块内多次等待,对于执行的每个阶段,它们的异常都可以汇聚到同一个catch(...)块中,而无需编写大量明确执行此操作的代码。 - Theo Yaung
4
async/await 的最后一个值得注意的部分是它是一个“更高级”的概念,而 ContinueWith(...) 则更为手动,明确地拥有 lambda、委托创建等。通过更高级的概念,可以提供更多的优化机会——例如,同一方法中的多个 awaits 实际上“共享”同一个 lambda 闭包(就像单个 lambda 的开销),而每次调用 ContinueWith(...) 都会获得开销,因为您明确编写了一个 lambda,所以编译器会为您提供它。 - Theo Yaung
1
@MobyDisk:澄清一下,await仍然像以前一样捕获SynchronizationContext.Current。但是在ASP.NET Core上,SynchronizationContext.Currentnull - Stephen Cleary
显示剩余3条评论

9

这基本上就是那样,但生成的代码不仅如此。如果想了解更多有关生成代码的详细信息,我强烈推荐Jon Skeet的Eduasync系列:

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

特别是第7篇文章涉及到(截至CTP 2)生成的内容以及原因,因此可能非常适合您目前正在寻找的内容:

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

编辑:我认为这可能比您从问题中寻找的更详细,但是如果您想知道在方法中有多个await时会出现什么情况,这在帖子#9中有所涵盖:)

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接