C#中的异步是如何工作的?

48

微软于2010年10月28日宣布推出了Visual Studio Async CTP,该工具引入了asyncawait关键字,用于在C#/VB中执行异步方法。

起初我认为编译器将这些关键字转换为线程的创建,但根据白皮书和Anders Hejlsberg在PDC演讲(31:00处)中所述,异步操作完全在主线程上进行。

如何在同一线程上并行执行操作?技术上如何实现,并将该功能转换为IL的什么内容?

3个回答

86

它的工作方式类似于C# 2.0中的yield return关键字。

异步方法实际上不是普通的顺序方法,而是编译成一个状态机(对象),具有一些状态(本地变量被转换为对象的字段)。在两个await使用之间的每个代码块都是状态机的一个“步骤”。

这意味着当该方法启动时,它只运行第一步,然后状态机返回并安排执行一些工作-当工作完成时,将运行状态机的下一步。例如,以下代码:

async Task Demo() { 
  var v1 = foo();
  var v2 = await bar();
  more(v1, v2);
}

大致翻译为:

class _Demo {
  int _v1, _v2;
  int _state = 0; 
  Task<int> _await1;
  public void Step() {
    switch(this._state) {
    case 0: 
      this._v1 = foo();
      this._await1 = bar();
      // When the async operation completes, it will call this method
      this._state = 1;
      op.SetContinuation(Step);
    case 1:
      this._v2 = this._await1.Result; // Get the result of the operation
      more(this._v1, this._v2);
  }
}
重要的部分在于它使用了 "SetContinuation" 方法来指定操作完成时应再次调用 "Step" 方法(该方法知道应使用 "_state" 字段运行原始代码的第二部分)。您可以轻松想象 "SetContinuation" 会是类似于 "btn.Click += Step" 的东西,这将完全在单个线程上运行。
C#中的异步编程模型非常接近于 F# 异步工作流程(实际上,除了一些技术细节外,它基本相同),并使用“async”编写响应式单线程GUI应用程序是一个相当有趣的领域 - 至少我认为是这样的 - 请参见例如此文章(现在也许我应该写一个 C# 版本 :-))。
翻译类似于迭代器(和“yield return”),事实上,以前就已经可以使用迭代器在C#中实现异步编程。我之前写过一篇关于此的文章 - 我认为它仍然可以为您提供有关如何进行翻译的一些洞见。

51

如何在同一线程上并行执行操作?

你无法这样做。异步不是“并行”或“并发”。异步可能会使用并行方式实现,也可能不会。它可能通过将工作分解为小块,将每个工作块放入队列中,然后在线程没有执行其他任务时执行每个工作块来实现。

我在我的博客上写了一系列关于所有这些内容如何运作的文章;直接与此问题相关的文章可能会在下周四发布。请访问此链接获取详细信息。


8
据我了解,asyncawait关键字的作用是,每当一个async方法使用await关键字时,编译器将把该方法的余下部分转换为一个继续执行的程序,并在异步操作完成时进行调度。这使得async方法可以立即返回给调用者,并在异步部分完成后恢复工作。
根据现有文件显示,其中有很多详细信息,但除非我弄错了,那就是它的要点。
在我看来,异步方法的目的不是并行运行大量代码,而是将异步方法切成若干个小块,按需要调用。关键点是编译器将处理所有复杂的回调电路,使用任务/继续。这不仅减少了复杂性,而且允许异步方法编写得更像传统的同步代码。

2
没有单独的线程,它是如何被调度的?在CLR中,连续性是一种特定的概念,它允许一些轻量级的调度吗? - Dirk Vollmar
2
@0xA3:我相信这篇论文说异步方法不会在自己的线程上运行。也就是说,就像TPL一样,它将根据情况混合使用当前线程和线程池线程。 - Brian Rasmussen

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