异步和等待关键字不会导致额外的线程被创建?

27
我感到困惑。一个或多个Task如何在单个线程上并行运行?我的理解显然是错误的。并行性的一些细节我无法理解,其中包括MSDN中的以下内容:
“async”和“await”关键字不会创建额外的线程。异步方法不需要多线程,因为异步方法不在自己的线程上运行。该方法在当前同步上下文上运行,并且仅在方法处于活动状态时才使用线程的时间。
..还有:
在启动任务和等待它之间,您可以启动其他任务。其他任务隐式并行运行,但不会创建额外的线程。
1个回答

26

它们不并行运行,而是轮流运行。 当正在运行的任务的进度被阻塞时,它会存储其状态并将控制权交给一个准备好的任务。 这是协作式多任务处理,而不是真正的并行。

线程基于相同的原理。 但是我想强调一些关键区别。

首先,仅仅因为 async/await 不是操作系统线程:

  • 任务不会看到不同的线程ID
  • 当任务交出控制权时,线程局部存储不会自动进行上下文切换。

其次,行为上的差异:

  • async/await 使用协作式多任务处理,Win32 线程使用抢占式。 所以必须使用 async/await 模型显式地将所有阻塞操作交出控制权。 因此,通过对不写入 yield 的函数进行阻塞调用,您可能会阻塞整个线程及其所有任务。
  • 任务在多处理系统上不会并行执行。 由于可控制重入性,这使得保持数据结构一致变得更加容易。

正如 Stephen 在评论中指出的那样,如果使用多线程同步上下文,您可以获得多个操作系统线程的同时执行(以及所有复杂性和潜在竞争条件)。 但 MSDN 引用的是单线程上下文情况。

最后,这种设计范例在其他地方也被使用,通过学习以下内容,您可以了解有关 async/await 的良好实践:

  • Win32 纤程(使用与线程相同的调用方式,但协作式)
  • Win32 重叠 I/O 操作,Linux aio
  • 协程

1
“blocked” 在这里可能是一个令人困惑的术语,因为异步的目标是避免阻塞。 - Cory Nelson
3
请注意,async 方法在被阻塞的情况下会保存其状态并产生输出,此时需要使用await。但如果它正在运行阻塞代码(例如 Thread.Sleep),则不会保存其状态和产生输出。 - Stephen Cleary
我认为应该明确说明微软为什么要这样写。许多人认为“async”会使方法异步运行(在单独的线程上)。但实际上并不是这样的。而且,仅仅因为使用了AWAIT,await SomeMethodAsync()也不会创建一个新线程。只有当SomeMethodAsync创建一个新线程时(它并不一定会这样做),才会创建一个新线程。 - igrimpe
是的,不争论正确性,只是建议减少“块”这个术语的歧义(就像Stephen所做的那样,+1)。 - Cory Nelson
4
大部分你更新的回答只适用于在单线程同步上下文(例如UI上下文)中执行的异步方法。如果它们在控制台应用程序中或从后台线程运行,则Task将在线程池上执行,并且除非您逐个await每个Task,否则将并行执行(在多个线程上)。 - Stephen Cleary
显示剩余3条评论

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