据我了解, await 关键字将暂停代码流,直到函数返回
嗯,是的,不是的。
- 是的,因为代码流在某种意义上确实停止了。
- 不,因为执行此代码流的线程不会阻塞。(同步调用
client.GetString()
会阻塞线程)。
实际上,它会返回到它的调用方法。要了解返回其调用方法的含义,您可以阅读另一个 C# 编译器魔法 -yield return
语句。
迭代器块yield return
会将方法分解为状态机 - 语句之后的代码仅在枚举器上调用yield return
之后才会执行。MoveNext()
(见这个和这个)。
现在,async/await
机制也基于类似的状态机(但是,它比yield return
状态机复杂得多)。
为了简化问题,让我们考虑一个简单的异步方法:
public async Task MyMethodAsync()
{
// code block 1 - code before await
// await stateement
var r = await SomeAwaitableMethodAsync();
// code block 2 - code after await
}
- 当您使用
async
标识符标记方法时,您会告诉编译器将该方法分解为状态机,并且您将await
进入该方法。
- 假设代码在线程上运行
Thread1
,您的代码调用 this MyMethodAsync()
。然后code block 1
将在同一个线程上同步运行。
SomeAwaitableMethodAsync()
也将被同步调用 - 但可以说该方法启动一个新的异步操作并返回一个Task
.
- 这是
await
进入画面的时候。它将代码流返回给它的调用者,并且线程Thread1
可以自由地运行调用者代码。那么在调用方法中发生的事情取决于调用方法await
是打开MyMethodAsync()
还是做其他事情 - 但重要的Thread1
是没有被阻塞。
- 现在剩下的 await 的魔力 - 当
SomeAwaitableMethodAsync()
最终返回的任务完成时,code block 2
计划运行。
async/await
建立在任务并行库之上——因此,这个调度是通过 TPL 完成的。
- 现在的问题是,这
code block 2
可能不会被安排在同一个线程上Thread1
,除非它SynchronizationContext
具有线程关联的活动(如 WPF/WinForms UI 线程)。await
知道SynchronizationContext
,因此,在被调用时code block 2
被安排在相同SynchronizationContext
的 (如果有的话)上。MyMethodAsync()
如果没有 active SynchronizationContext
,那么很可能code block 2
会在一些不同的线程上运行。
最后,我要说的是,由于async/await
它是基于编译器创建的状态机,比如yield return
,它有一些缺点——例如,你不能await
在一个finally
块内。
我希望这能消除你的疑虑。