所以我最近读了很多文章,试图了解异步方法在.NET世界中的工作原理。
我真的很难理解异步调用是如何工作的,从实际调用HttpClient.GetAsync("https://google.com")
,一直到操作系统如何在网卡上启动调用。令我困扰的是,状态机如何知道何时调用它的MoveNext()
方法。
从网卡上,我们有一个系统中断(一旦我们得到请求响应(,它会在短时间内控制CPU。CPU创建一个所谓的Deferred Procedure Call (DPC)
,操作系统内核跟踪并以高优先级执行这些DPC。之后,内核启动一个APC (asynchronous procedure call)
,换句话说,它通知进行异步调用的线程,并将Task
标记为完成,然后可以继续执行。
我的问题是:
在windows窗体应用程序中,我们有一个UI线程(它在屏幕上绘制窗口并响应用户事件(。这个UI线程是否有某种Task queue
,它会不时地进行互操作,并询问其中一个任务是否处于"已完成"状态?如果是,那么一切都有意义,我最初的问题就得到了回答。一旦操作系统将任务设置为已完成,并且UI线程看到了这一点,它将在其状态机上调用MoveNext()
方法。但我最近听说UI线程没有这种Task queue
。如果是这种情况,那么操作系统和应用程序本身内部如何知道如何触发MoveNext()
方法?
我做的一个假设是,操作系统在当前可用的任何线程中注入MoveNext()
的代码。但这仍然很奇怪。
将Task标记为已完成,并可以继续执行。
先备份一点。如果你还没有读过我的博客,我建议你读一下。
当async
方法执行await
时,它首先检查操作是否完成。在这种情况下,通常不会(但假设这一点是不明智的;尤其是移动平台会大量缓存网络请求,可以立即完成这些请求(。因此,假设GetAsync
返回的任务是不完整的;在这种情况下,async
方法注册该任务的延续。
但它并不是,只是注册MoveNext
;默认情况下,async
方法捕获其当前上下文(SynchronizationContext
或TaskScheduler
(,并注册在该上下文上继续执行async
方法的委托。如果没有上下文,则将继续调度到线程池。
但我最近听说UI线程没有这种任务队列。如果是这种情况,那么操作系统和应用程序本身内部如何知道如何触发MoveNext((方法?
这是正确的。UI线程没有任何任务队列的概念,也没有它正在等待的任务类型的概念。但它确实有一个消息队列;这就是";主环路";UI线程的。操作系统向它发送关于鼠标点击和键盘按压以及其他一百件事情的信息。它还支持自定义(应用程序定义的(消息。这些自定义消息类型之一是";在UI线程上运行该委托代码";。
UI线程也有一个SynchronizationContext
——有时会在调试器中检出SynchronizationContext.Current
。这个SynchronizationContext
可以用来通过向自己发送一条自定义消息来执行特定的委托,来调度UI线程上的工作。
因此,async
方法将捕获当前的SynchronizationContext
(如果存在(,并使用它来调度该方法的继续;由于它是一个UI上下文,这意味着继续作为消息发送到消息循环,UI线程将(最终(在其中处理它,继续UI线程上的async
方法。