如果某些代码“等待”并且单线程继续运行程序,那么被暂停的位如何恢复活力?单线程是否需要返回轮询暂停代码的状态以查看其是否完成?
3 回答
MSDN 文档和我的介绍性async
博客文章都回答了这个问题。
总之,当一个async
方法遇到一个await
尚未完成的操作时,默认情况下它会捕获当前上下文。这个“上下文”是当前的SynchronizationContext
,除非它是null
,在这种情况下它是当前的TaskScheduler
。稍后,当操作完成时,该async
方法的其余部分将调度到该上下文。
在 UI 应用程序中,这可以是 UI SynchronizationContext
(在 UI 线程上调度方法)。在 ASP.NET 应用程序中,这通常是一个请求上下文(未绑定到特定线程)。如果您在async
线程池上运行一个方法,它通常是线程池上下文(同样,不绑定到任何特定线程)。
回到 UI 示例,UI 线程确实有一个它运行的中央“消息循环”,处理来自其队列的 Win32 消息。UISynchronizationContext
只是向队列发布一条消息,告诉 UI 线程执行该async
方法的下一部分。
[...] 被暂停的部分如何恢复生机?
通过使用委托(取自msdn)。
“await”关键字告诉编译器将可能的挂起/恢复点插入标记为“async”的方法中。
从逻辑上讲,这意味着当你写“await someObject;”时 编译器将生成代码来检查 someObject 表示的操作是否已经完成。如果有,则在等待点上同步继续执行。如果没有,生成的代码会将一个延续委托连接到等待的对象,这样当表示的操作完成时,将调用该延续委托。这个延续委托将重新进入该方法,在上一次调用停止的这个等待位置处拾取。此时,无论等待的对象在等待时是否已经完成,都会从对象中提取任何结果,或者如果操作失败,则传播发生的任何异常。
单线程是否需要返回轮询暂停代码的状态以查看其是否完成?
不,没有这样的事情。当您await
从当前线程调用控件时,调用堆栈会向上走一步,即它返回给包含await
. 任务完成后,TPL 调用委托方法,该方法从await
线路恢复控制。
我实际上不知道具体是如何await
在 C# 中实现的,但让我提出一种可能的方式,只是为了回答“这怎么可能?”这个问题。(与“如何实施”相反?)。
首先考虑yield
关键字,这是完全不同的,但我认为更普遍理解。当您编写一个在函数体中返回IEnumerable<T>
和使用yield return
的函数时,我们大多数人现在都明白 C# 编译器会为您生成一个迭代器类,并生成看起来与您编写的代码非常不同的 IL。我的观点是我们必须意识到 C# 代码本身并不总是很清楚地映射到一组 IL 指令;有时转换很复杂。
情况也必须如此await
,其中 C# 编译器不会简单地为 C# 代码的每一行发出一堆简单的步骤。例如,它可以做的是调用 with ,生成一个调用给定方法的委托,然后将方法的其余await
代码捆绑为委托的回调。
其他方法如何异步操作对await
关键字无关紧要。它可能在单独的线程上执行工作,或者执行一些 I/O 操作。无论它做什么,它都必须符合返回 a 的异步模式Task<T>
;这就是await
依赖。
在将剩余逻辑添加为侦听器以在单独线程上发生的工作完成时接收信号之后,在 IL 中生成的函数将返回。然后,该单独的线程会将消息发送回原始线程(在大多数情况下,这实际上是一个消息循环),以及对侦听器的引用。