.NET6中的await原理浅析
前言
看过不少关于 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设置 -1stateMachine._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;
}
}
总结
Task.Run创建Task, 把委托放在m_action字段, 把Task压入线程池队列,等待调度_builder.AwaitUnsafeOnCompleted(ref awaiter, ref stateMachine);尝试把状态机对象放在Task.m_continuationObject字段上,等待线程池线程调度完成任务后使用(用来执行后续),若操作失败,直接把状态机对象压入线程池队列,等待调度- 线程池线程调度任务完成后,会判断
Task.m_continuationObject有值,直接执行它的MoveNext
备注
状态机实现中,尝试修改
Task.m_continuationObject,可能会失败,
就会直接把状态机对象压入线程池, 但是线程池调度,不都是判断是不是Task类型么, 其实状态机的包装类是Task的子类,哈哈,是不是明白了class AsyncStateMachineBox<TStateMachine> : Task<TResult>, IAsyncStateMachineBox where TStateMachine : IAsyncStateMachine static 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
.NET6中的await原理浅析的更多相关文章
- 关于 ReentrantLock 中锁 lock() 和解锁 unlock() 的底层原理浅析
关于 ReentrantLock 中锁 lock() 和解锁 unlock() 的底层原理浅析 如下代码,当我们在使用 ReentrantLock 进行加锁和解锁时,底层到底是如何帮助我们进行控制的啦 ...
- Javascript自执行匿名函数(function() { })()的原理浅析
匿名函数就是没有函数名的函数.这篇文章主要介绍了Javascript自执行匿名函数(function() { })()的原理浅析的相关资料,需要的朋友可以参考下 函数是JavaScript中最灵活的一 ...
- Mysql 中的MVCC原理,undo日志的依赖
一. MVCC 原理了解 原文点击:MVCC原理浅析 读锁: 也叫共享锁.S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的 ...
- JAVA 8 主要新特性 ----------------(二)版本中数据结构的修改浅析
一.版本中数据结构的修改浅析1.HashMap.HashSet.ConcurrentHashMap的数据结构发生变化 (1)HashMap简介(结构:哈希表+链表) HashMap存储的数据是无序的, ...
- [转帖]Git数据存储的原理浅析
Git数据存储的原理浅析 https://segmentfault.com/a/1190000016320008 写作背景 进来在闲暇的时间里在看一些关系P2P网络的拓扑发现的内容,重点关注了Ma ...
- Android-Binder原理浅析
Android-Binder原理浅析 学习自 <Android开发艺术探索> 写在前头 在上一章,我们简单的了解了一下Binder并且通过 AIDL完成了一个IPC的DEMO.你可能会好奇 ...
- Dubbo学习(一) Dubbo原理浅析
一.初入Dubbo Dubbo学习文档: http://dubbo.incubator.apache.org/books/dubbo-user-book/ http://dubbo.incubator ...
- 沉淀,再出发:docker的原理浅析
沉淀,再出发:docker的原理浅析 一.前言 在我们使用docker的时候,很多情况下我们对于一些概念的理解是停留在名称和用法的地步,如果更进一步理解了docker的本质,我们的技术一定会有质的进步 ...
- 阻塞和唤醒线程——LockSupport功能简介及原理浅析
目录 1.LockSupport功能简介 1.1 使用wait,notify阻塞唤醒线程 1.2 使用LockSupport阻塞唤醒线程 2. LockSupport的其他特色 2.1 可以先唤醒线程 ...
- 【Spark Core】TaskScheduler源代码与任务提交原理浅析2
引言 上一节<TaskScheduler源代码与任务提交原理浅析1>介绍了TaskScheduler的创建过程,在这一节中,我将承接<Stage生成和Stage源代码浅析>中的 ...
随机推荐
- 【工具】-Misc-Python-dsstore
介绍 这是一个.DS_Store解析工具. 什么是.DS_Store .DS_Store 是 Desktop Services Store 的缩写,是 macOS 操作系统上的一个不可见文件,只要您使 ...
- 【opencv】传统图像识别:hog+svm行人识别实战
实战工具:python3.7+pycharm+opencv4.6算法知识:HOG特征提取.SVM模型构建实战目的:本次实战的目的是熟悉HOG+SVM工作流算法,初步掌握图像分类的传统算法.实战记录:本 ...
- 零代码,使用 Dify 和 Laf 两分钟接入企业微信 AI 机器人
Dify 允许创建 AI 应用,并提供二次开发的能力.这里我将演示创建一个法律问答助手的 AI 应用,称作"知法".在本篇教程中,我将指导你为"知法"接入企业微 ...
- 《SQL与数据库基础》07. 约束
目录 约束 常见约束案例 外键约束 删除/更新行为 本文以 MySQL 为例 约束 概念:约束是作用于表中字段上的规则,用于限制存储在表中的数据. 目的:保证数据库中数据的正确性.有效性和完整性. 分 ...
- Vue【原创】下划线动态效果按钮,一般按钮模式,开关切换模式
效果图: 1.icon-button 一般按钮模式: 1 <template> 2 <div class="icon-button" :style="{ ...
- legend的用法
常规使用legend的方法 1. 自动检测 设置title的label标签,随后使用不带参数的legend函数也会自动显示(但不限于一种方式设置图形的label,只要设置了图形的label,使用leg ...
- 算法笔记_python
目录 算法 概念 时间复杂度 空间复杂度 递归原理 顺序查找 二分查找 列表排序 LowB 三人组 冒泡排序 选择排序 插入排序 NB三人组 快速排序 堆排序 归并排序 NB三人组小结 总结 其他排序 ...
- python入门基础(13)--类、对象
面向过程的编程语言,如C语言,所使用的数据和函数之间是没有任何直接联系的,它们之间是通过函数调用提供参数的形式将数据传入函数进行处理. 但可能因为错误的传递参数.错误地修改了数据而导致程序出错,甚至是 ...
- 文盘Rust -- tonic-Rust grpc初体验
gRPC 是开发中常用的开源高性能远程过程调用(RPC)框架,tonic 是基于 HTTP/2 的 gRPC 实现,专注于高性能.互操作性和灵活性.该库的创建是为了对 async/await 提供一流 ...
- assembleDebug太慢的问题调查以及其他
Preface 最近在做flutter上的音频和视频方面的探索. 需要用到一些视屏区域截取,视屏导出成序列图等等. 这是昨天晚上到今天早上解决的一些问题的汇总,可能先后顺序之类的会记错: 此文目的用于 ...