因此,我最近阅读了很多文章,试图了解异步方法在.NET世界中的工作原理.

我真的很努力地了解异步呼叫是如何工作的,从实际呼叫HttpClient.GetAsync("https://google.com"),一直到操作系统如何在网卡上发起呼叫. 让我困扰的是状态机如何知道何时调用它的MoveNext()方法.

在网卡上,我们有一个系统中断(一旦我们得到请求响应),它会在短时间内控制CPU.CPU创建一个所谓的Deferred Procedure Call (DPC),OS内核跟踪并以高优先级执行这些DPC.在此之后,内核启动APC (asynchronous procedure call),换句话说,它通知进行异步调用的线程,并将Task标记为已完成,并且可以恢复其执行.

我的问题是:

在Windows窗体应用程序中,我们有一个UI线程(它在屏幕上绘制窗口并响应用户事件).这个UI线程是否有某种类型的Task queue,它会不时地询问是否有一个任务处于已完成状态?如果是,那么一切都有意义,我最初的问题也得到了回答.一旦操作系统将任务设置为已完成,并且UI线程看到了这一点,它将调用其状态机上的MoveNext()方法. 但我最近听说UIThread没有这样的Task queue.如果是这样的话,操作系统和应用程序本身怎么知道如何触发MoveNext()方法呢?

我所做的一个假设是,操作系统在当前可用的任何线程中注入MoveNext()的代码.但这仍然很奇怪.

推荐答案

将任务标记为已完成,并可以恢复其执行.

先往后退一点.我推荐reading this blog entry of mine if you haven't already个.

async方法执行await时,它首先判断操作是否完成.在这种情况下,通常不会这样(但假设这一点是不明智的;尤其是移动平台大量缓存网络请求,can会立即完成它们).因此,让我们假设从GetAsync返回的任务是未完成的;在这种情况下,async方法注册该任务的延续.

但不是just注册MoveNext;默认情况下,async方法捕获它们的当前上下文(SynchronizationContextTaskScheduler),并注册继续执行async方法on that context的委托.如果没有上下文,则继续被调度到线程池.

但我最近听说,UI线程没有这种任务队列.如果是这样,那么操作系统和应用程序本身如何知道如何触发MoveNext()方法呢?

这是正确的.UI线程没有任何任务队列的概念,也不知道它在等待什么类型的任务.但它有一个消息队列;这是UI线程的"主循环".操作系统会向它发送关于鼠标点击和键盘按压的消息,以及其他上百种信息.它还支持定制(应用程序定义的)消息.其中一种自定义消息类型是"在UI线程上运行此委托代码".

UI线程有时也在调试器中具有SynchronizationContext-签出SynchronizationContext.Current.并通过向自身发送执行特定委托的定制消息来实现这一点.

因此,async方法将捕获当前的SynchronizationContext(如果存在),并使用它来调度方法的继续;因为它是一个UI上下文,这意味着继续作为消息发送到消息循环,在那里UI线程将(最终)处理它,继续UI线程上的async方法.

.net相关问答推荐

找不到包Microsoft.VisualStudio.Azure.Containers.Tools.Targets

.NET MAUI ListView - ObservableCollection - 在异步方法期间不更新

避免函数和其他对象之间的相互递归的模式?

dotnet ef dbcontext scaffold command --data-annotations 或 -d 命令行参数似乎不起作用

设置日志(log)文件名以在 Log4j 中包含当前日期

每 X 秒执行一次指定函数

log4net 与 TraceSource

使用 EPPlus 时如何设置列类型

LINQ 性能常见问题解答

C# 中 try/finally 的开销?

C# 中基于接口编程的运算符重载

Java 和 .NET 技术/框架的类似物

在 C# 中将字符串转换为十六进制字符串

如何在 WPF 中的 Xaml 文件中添加注释?

为什么 double.NaN 不等于自身?

MemoryCache 不遵守配置中的内存限制

如何获取当前的 ProcessID?

如何将 .NET 应用程序编译为本机代码?

.NET 中的 java.lang.IllegalStateException?

如何使用 AutoMapper .ForMember?