等待/异步与“经典”异步(回调)

所以新的异步CTP非常酷; 它使我的生活变得更加容易,不必编写命名的回调方法,并使方法的意图更加清晰。

现在我已经开始玩了一下,我想知道async / await和“经典”异步回调语法之间可能存在什么差异。

这里有一些我想到的问题,但是还有很多其他我现在都不会想到的,可能会在以后。

  • 是否有人提供优于另一个的优越表现?
  • 一个比另一个更大的开销是否存在?
  • 哪个在高性能环境中使用会更好?

答案很复杂,await的当前编译器实现在几个方面比回调更好,但在某些情况下更糟。

.NET执行上下文 :我们打算await和ContinueWith(…)捕获和恢复.NET执行上下文。 否则它不会通过.NET安全要求,因为那时你可以采取任意的东西,如凭证等,并将它们留在线程池中,用于下一个工作项。 对于’await’,这是我们在内部构建中进行的调整,但是在我们生成// BUILD开发人员预览之后。

内存分配 :在几种方式中,’await’在内存分配上比手动回调更好。 关键是对于有很多等待的函数,你实际生成的东西相当于几个回调。 如果你有5个等待线性执行顺序,并且执行总是流到最后,则等效将需要5个回调。 对于这5个回调中的每一个,都可以生成一个单独的lambda闭包对象和一个代表该特定lambda的委托。 在’await’的情况下,编译器知道你不会将委托对象用于其他任何事情。 因此,整个方法共享1个闭包和1个委托,并使用内部状态机来跟踪您在方法中的位置。 因此,对于这种情况,’await’分配更少的对象,实际上可以加速你的程序,因为太多的对象= GC必须花费更多的时间来计算出什么是活的/死的。

短线 ‘Await’也具有比回调更奇妙的语义。 在您创建回调lambda的情况下,无论如何,编译器都被强制分配闭包和lambda的入口点委托。 对于’await’,await合同允许为已经“完成”的等待事件提供更优化的代码路径。 如果awa在await得到评估之前说它已经“完成”了,那么语义只是一个纯粹的传递来扼杀结果。 这意味着编译器有机会延迟分配,直到您真正需要它为止,因此您永远不会支付闭包分配,委托分配和调度成本,除非您确实需要它。 当前的Developer Preview编译器包括这些性能优化。

对perf的交易危险如果你真的想绕过.NET安全模型,你可以想象一下如果你绝对相信你永远不会通过避免执行上下文包/恢复来获得一点性能的情况需要捕获/恢复上下文。 但是,大多数.NET的方法都会默默地执行此操作,因此您确实需要知道哪些方法可以在没有它的情况下为您提供原始访问权限。 .NET的经验法则是,如果API在部分信任(例如Silverlight)中可用,那么API在调用时肯定会捕获上下文,然后恢复它,如果它是在其他地方转移执行的API(例如ContinueWith,QueueUserWorkItem( ……)等)。 如果你推出自己的线程池,只是排队代表,你可以绕过这个,但很可能你不需要它。

我的个人推荐使用等待。 这是更高的水平,这是你想要的。 我们已经付出了相当大的努力来尝试为此版本调整它,我们可能会进一步调整它。 基于回调的API将更具限制性,因为编译器在开始破坏语言规则之前只能调整太多。 等待一种方法可以让你拥有比回调更聪明的闭包。 AND … await比回调更直观易读/使用:)

像匿名函数和迭代器一样, asyncawait关键字是语法糖。 从技术意义上讲,它们的效率不低于等效的非含糖版本。 他们只是为你节省了很多打字。