当前位置: 首页 > news >正文

聊一聊 C#异步 任务延续的三种底层玩法

一:背景

1. 讲故事

最近聊了不少和异步相关的话题,有点疲倦了,今天再写最后一篇作为近期这类话题的一个封笔吧,下篇继续写我熟悉的 生产故障 系列,突然亲切感油然而生,哈哈,免费给别人看程序故障,是一种积阴德阳善的事情,欲知前世因,今生受者是。欲知来世果,今生做者是。

在任务延续方面,我个人的总结就是三类,分别为:

  1. StateMachine
  2. ContinueWith
  3. Awaiter

话不多说,我们逐个研究下底层是咋玩的?

二:异步任务延续的玩法

1. StateMachine

说到状态机大家再熟悉不过了,也是 async,await 的底层化身,很多人看到 async await 就想到了IO场景,其实IO场景和状态机是两个独立的东西,状态机是一种设计模式,把这个模式套在IO场景会让代码更加丝滑,仅此而已。为了方便讲述,我们写一个 StateMachine 与 IO场景 无关的一段测试代码。

internal class Program{static void Main(string[] args){UseAwaitAsync();Console.ReadLine();}static async Task<string> UseAwaitAsync(){var html = await Task.Run(() =>{Thread.Sleep(1000);var response = "<html><h1>博客园</h1></html>";return response;});Console.WriteLine($"GetStringAsync 的结果:{html}");return html;}}

那这段代码在底层是如何运作的呢?刚才也说到了asyncawait只是迷惑你的一种幻象,我们必须手握辟邪宝剑斩开幻象显真身,这里借助 ilspy 截图如下:

从卦中看,本质上就是借助AsyncTaskMethodBuilder<string> 建造者将 awaiter 和 stateMachine 做了一个绑定,感兴趣的朋友可以追一下 AwaitUnsafeOnCompleted() 方法,最后状态机 <UseAwaitAsync>d__1 实例会放入到 Task.Run 的 m_continuationObject 字段。如果有朋友对流程比较蒙的话,我画了一张简图。

图和代码都有了,接下来就是眼见为实。分别在 AddTaskContinuationRunContinuations 方法中做好埋点,前者可以看到 延续任务 是怎么加进去的,后者可以看到 延续任务 是怎么取出来的。


心细的朋友会发现这卦上有一个很特别的地方,就是 allowInlining=true,也就是回调函数(StateMachine)是在当前线程上一撸到底的。

有些朋友可能要问,能不能让延续任务 跑在单独线程上? 可以是可以,但你得把 Task.Run 改成 Task.Factory.StartNew ,这样就可以设置TaskCreationOptions参数,参考代码如下:

    var html = await Task.Factory.StartNew(() =>{}, TaskCreationOptions.RunContinuationsAsynchronously);

2. ContinueWith

那些同处于被裁的35岁大龄程序员应该知道Task是 framework 4.0 时代出来的,而async,await是4.5出来的,所以在这个过渡期中有大量的项目会使用ContinueWith 导致回调地狱。。。 这里我们对比一下两者有何不同,先写一段参考代码。

internal class Program{static void Main(string[] args){UseContinueWith();Console.ReadLine();}static Task<string> UseContinueWith(){var query = Task.Run(() =>{Thread.Sleep(1000);var response = "<html><h1>博客园</h1></html>";return response;}).ContinueWith(t =>{var html = t.Result;Console.WriteLine($"GetStringAsync 的结果:{html}");return html;});return query;}}

从卦代码看确实没有asyncawait简洁,那 ContinueWith 内部做了什么呢?感兴趣的朋友可以跟踪一下,本质上和 StateMachine 的玩法是一样的,都是借助 m_continuationObject 来实现延续,画个简图如下:

代码和模型图都有了,接下来就是用 dnspy 开干了。。。还是在 AddTaskContinuationRunContinuations 上埋伏断点观察。


从卦中可以看到,延续任务使用新线程来执行的,并没有一撸到底,这明显与 asyncawait 的方式不同,有些朋友可能又要说了,那如何实现和StateMachine一样的呢?这就需要在 ContinueWith 中新增 ExecuteSynchronously 同步参数,参考如下:

    var query = Task.Run(() => { }).ContinueWith(t =>{}, TaskContinuationOptions.ExecuteSynchronously);

3. Awaiter

使用Awaiter做任务延续的朋友可能相对少一点,它更多的是和 StateMachine 打配合,当然单独使用也可以,但没有前两者灵活,它更适合那些不带返回值的任务延续,本质上也是借助 m_continuationObject 字段实现的一套底层玩法,话不多说,上一段代码:

static Task<string> UseAwaiter(){var awaiter = Task.Run(() =>{Thread.Sleep(1000);var response = "<html><h1>博客园</h1></html>";return response;}).GetAwaiter();awaiter.OnCompleted(() =>{var html = awaiter.GetResult();Console.WriteLine($"UseAwaiter 的结果:{html}");});return Task.FromResult(string.Empty);}

前面两种我配了图,这里没有理由不配了,哈哈,模型图如下:

接下来把程序运行起来,观察截图:


从卦中观察,它和StateMachine一样,默认都是 一撸到底 的方式。

三:RunContinuations 观察

这一小节我们单独说一下 RunContinuations 方法,因为这里的实现太精妙了,不幸的是Dnspy和ILSpy反编译出来的代码太狗血,原汁原味的简化后代码如下:

    private void RunContinuations(object continuationObject) // separated out of FinishContinuations to enable it to be inlined{bool canInlineContinuations =(m_stateFlags & (int)TaskCreationOptions.RunContinuationsAsynchronously) == 0 &&RuntimeHelpers.TryEnsureSufficientExecutionStack();switch (continuationObject){// Handle the single IAsyncStateMachineBox case.  This could be handled as part of the ITaskCompletionAction// but we want to ensure that inlining is properly handled in the face of schedulers, so its behavior// needs to be customized ala raw Actions.  This is also the most important case, as it represents the// most common form of continuation, so we check it first.case IAsyncStateMachineBox stateMachineBox:AwaitTaskContinuation.RunOrScheduleAction(stateMachineBox, canInlineContinuations);LogFinishCompletionNotification();return;// Handle the single Action case.case Action action:AwaitTaskContinuation.RunOrScheduleAction(action, canInlineContinuations);LogFinishCompletionNotification();return;// Handle the single TaskContinuation case.case TaskContinuation tc:tc.Run(this, canInlineContinuations);LogFinishCompletionNotification();return;// Handle the single ITaskCompletionAction case.case ITaskCompletionAction completionAction:RunOrQueueCompletionAction(completionAction, canInlineContinuations);LogFinishCompletionNotification();return;}}

卦中的 case 挺有意思的,除了本篇聊过的 TaskContinuation 和 IAsyncStateMachineBox 之外,还有另外两种 continuationObject,这里说一下 ITaskCompletionAction 是怎么回事,其实它是 Task.Result 的底层延续类型,所以大家应该能理解为什么 Task.Result 能唤醒,主要是得益于Task.m_continuationObject =completionAction 所致。

说了这么说,如何眼见为实呢?可以从源码中寻找答案。

private bool SpinThenBlockingWait(int millisecondsTimeout, CancellationToken cancellationToken){var mres = new SetOnInvokeMres();AddCompletionAction(mres, addBeforeOthers: true);var returnValue = mres.Wait(Timeout.Infinite, cancellationToken);}private sealed class SetOnInvokeMres : ManualResetEventSlim, ITaskCompletionAction{internal SetOnInvokeMres() : base(false, 0) { }public void Invoke(Task completingTask) { Set(); }public bool InvokeMayRunArbitraryCode => false;}

从卦中可以看到,其实就是把 ITaskCompletionAction 接口的实现类 SetOnInvokeMres 塞入了 Task.m_continuationObject 中,一旦Task执行完毕之后就会调用 Invoke() 下的 Set() 来实现事件唤醒。

四:总结

虽然异步任务延续有三种实现方法,但底层都是一个套路,即借助 Task.m_continuationObject 字段玩出的各种花样,当然他们也是有一些区别的,即对 m_continuationObject 任务是否用单独的线程调度,产生了不同的意见分歧。

相关文章:

聊一聊 C#异步 任务延续的三种底层玩法

一&#xff1a;背景 1. 讲故事 最近聊了不少和异步相关的话题&#xff0c;有点疲倦了&#xff0c;今天再写最后一篇作为近期这类话题的一个封笔吧&#xff0c;下篇继续写我熟悉的 生产故障 系列&#xff0c;突然亲切感油然而生&#xff0c;哈哈&#xff0c;免费给别人看程序故…...

(k8s)Flannel Error问题解决!

1.问题描述 书接上回&#xff0c;我们在解决kubectl不断重启的时候引入了Flannel 网络插件&#xff0c;但是一上来就报错&#xff0c; 2.问题解决 自己的思路&#xff1a;照例开始检查 1.先检查一下目前Flannel的pod kubectl get pods --all-namespaces 2.检查 Flannel的po…...

Delaunay三角刨分算法理解及c#过程实现

Delaunay三角刨分算法理解及c#过程实现 0 引言1 关于三角剖分2 Delaunay三角剖分算法实现及对比3 结语0 引言 💻💻AI一下💻💻 三角剖分是什么? 三角剖分是一种将平面或曲面划分成三角形集合的方法。在二维平面中,给定一个平面区域(可以是多边形等),通过连接区域…...

Backend - ADO.NET(C# 操作Oracle、PostgreSQL DB)

目录 一、引入参考 1. ConfigurationManager的调用前提&#xff1a; 2. NpgsqlConnection的调用前提&#xff1a; 3. OracleConnection的调用前提&#xff1a; 二、设置数据库链接字串 1. 在App.config中设定链接数据库详情 2. 获取数据库链接字串 三、调用 1.调用Oracle数据库…...

Idea-离线安装SonarLint插件地址

地址&#xff1a; SonarQube for IDE - IntelliJ IDEs Plugin | Marketplace 选择Install Plugin from Disk..&#xff0c;选中下载好的插件&#xff0c;然后重启idea...

Leetcode Hot100 第三题 234. 回文链表

用快慢指针找到链表中间节点反转后面一段链表遍历每个节点做判断为什么是while pre: 不能写while head呢 ? 答&#xff1a;因为slow节点在反转后&#xff0c;他的前序节点除了反转之后的节点&#xff0c;之前正序的节点仍然存在的&#xff0c;即slow.pre 的next依旧是slow, 我…...

Python教程丨Python环境搭建 (含IDE安装)——保姆级教程!

工欲善其事&#xff0c;必先利其器。 学习Python的第一步不要再加收藏夹了&#xff01;提高执行力&#xff0c;先给自己装好Python。 1. Python 下载 1.1. 下载安装包 既然要下载Python&#xff0c;我们直接进入python官网下载即可 Python 官网&#xff1a;Welcome to Pyt…...

SpringBoot项目实战(39)--Beetl网页HTML文件中静态图片及CSS、JS文件的引用和展示

使用Beetl开发网页时&#xff0c;在网页中使用的CSS、JS、图片等静态资源需要进行适当的配置才可以展示。大致的过程如下&#xff1a; &#xff08;1&#xff09;首先Spring Security框架需要允许js、css、图片资源免授权访问。 &#xff08;2&#xff09;网站开发时&#xff0…...

ARIMA模型 (AutoRegressive Integrated Moving Average) 算法详解与PyTorch实现

ARIMA模型 (AutoRegressive Integrated Moving Average) 算法详解与PyTorch实现 目录 ARIMA模型 (AutoRegressive Integrated Moving Average) 算法详解与PyTorch实现1. ARIMA模型概述1.1 时间序列预测1.2 ARIMA的优势2. ARIMA的核心技术2.1 自回归 (AR)2.2 差分 (I)2.3 移动平…...

【Uniapp-Vue3】swiper滑块视图容器的用法

我们使用swiper标签就可以实现轮播图的效果。 一、swiper组件的结构 整体的轮播图使用swiper标签&#xff0c;轮播的每一页使用swiper-item标签。 <template><swiper class"swiper"><swiper-item><view class"swiper-item">111…...

allure报告修改默认语言为中文

1、项目根目录创建.py文件&#xff0c;把代码复制进去 import os from pathlib import Pathdef create_settings_js_file(directory"../pytest_mytt/reports/allures/", filenamesettings.js):# 创建或确认目录存在Path(directory).mkdir(parentsTrue, exist_okTrue…...

国产3D CAD将逐步取代国外软件

在工业软件的关键领域&#xff0c;计算机辅助设计&#xff08;CAD&#xff09;软件对于制造业的重要性不言而喻。近年来&#xff0c;国产 CAD 的发展态势迅猛&#xff0c;展现出巨大的潜力与机遇&#xff0c;正逐步改变着 CAD 市场长期由国外软件主导的格局。 国产CAD发展现状 …...

GolangWeb开发- net/http模块

文章目录 Golang开发-案例整理汇总一、net/http介绍二、HTTP客户端Get请求Post请求三、HTTP服务端总结Golang开发经典案例,点击下方链接 Golang开发-案例整理汇总 一、net/http介绍 Go语言内置的net/http包提供了HTTP客户端和服务端的实现。 文档链接: https://pkg.go.dev/n…...

Vue2中使用Echarts

1.安装echarts 在项目根目录下&#xff0c;使用npm或yarn安装ECharts&#xff1a; npm install echarts --save 或者 yarn add echarts 2.在相应的vue页面中引入echarts <script> import * as echarts from "echarts"; </script> 3.代码解析 <…...

AI赋能服装零售:商品计划智能化,化危机为转机

在服装零售这片竞争激烈的战场上&#xff0c;每一个细微的决策都可能成为品牌兴衰的关键。当市场波动、消费者口味变化、供应链挑战接踵而至时&#xff0c;许多品牌往往将危机归咎于外部环境。然而&#xff0c;真相往往更为深刻——“危机不是外部的&#xff0c;而是你的商品计…...

Spring AI ectorStore

Spring AI中的VectorStore是一种用于存储和检索高维向量数据的数据库或存储解决方案&#xff0c;它在AI应用中扮演着至关重要的角色。以下是对Spring AI VectorStore的详细解析&#xff1a; 一、VectorStore的基本概念 定义&#xff1a;VectorStore特别适用于处理那些经过嵌入…...

zig 安装,Hello World 示例

1. 安装 Zig 首先&#xff0c;你需要在你的计算机上安装 Zig 编译器。你可以从 Zig 官方网站 下载适合你操作系统的版本。 安装完成后&#xff0c;你可以在终端中运行以下命令来检查 Zig 是否安装成功&#xff1a; zig version如果一切正常&#xff0c;它会显示 Zig 的版本信…...

龙蜥Linux系统部署docker21.1.3版本

龙蜥系统配置docker环境 更新yum源 更新软件源中的包。 yum update安装底层工具 yum install -y yum-utils device-mapper-persistent-data lvm2添加阿里云仓库 # 添加阿里云的docker镜像仓库 yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/c…...

django解决跨域问题

# 1.安装django-cors-headers 库 pip install django-cors-headers -i https://pypi.tuna.tsinghua.edu.cn/simple2.添加到应用程序中 添加 corsheaders 到你的 INSTALLED_APPS 设置中&#xff1a; INSTALLED_APPS [...corsheaders,... ]3.添加中间件 MIDDLEWARE [...cor…...

【蓝桥杯选拔赛真题60】C++寻宝石 第十四届蓝桥杯青少年创意编程大赛 算法思维 C++编程选拔赛真题解

目录 C++寻宝石 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 五、运行结果 六、考点分析 七、推荐资料 C++寻宝石 第十四届蓝桥杯青少年创意编程大赛C++选拔赛真题 一、题目要求 1、编程实现 有N(1<N<100)个盒子排成一排,每个盒子都放…...

协议转换网关与数据采集网关的区别与差异

摘要在工业自动化、物联网、智能建筑等领域中&#xff0c;“协议转换”和“数据采集网关”是两个常被提及但容易混淆的概念。它们虽有关联&#xff0c;却扮演着不同的角色。理解其核心差异对于构建高效、可靠的数据通信系统至关重要。1.核心定义&#xff1a;本质差异1.1协议转换…...

为什么你的Jellyfin需要MaxSubtitle:5分钟实现智能字幕匹配的终极指南

为什么你的Jellyfin需要MaxSubtitle&#xff1a;5分钟实现智能字幕匹配的终极指南 【免费下载链接】jellyfin-plugin-maxsubtitle 一个 Jellyfin 中文字幕插件&#xff08;未来可以不局限中文&#xff09; 项目地址: https://gitcode.com/gh_mirrors/je/jellyfin-plugin-maxs…...

从0到1:产品经理如何构建高效的产品管理体系

现如今&#xff0c;在数字化浪潮把全球都给席卷的这种状况之下&#xff0c;产品已然变成了企业竞争的核心载体。对于一个优秀的产品来讲&#xff0c;其背后通常是没办法离开一套科学且高效的产品管理体系的。产品管理&#xff0c;它作为连接用户需求、商业目标以及技术实现的枢…...

RA6M3 HMI开发板SDHI接口与SD卡存储性能深度测评

1. 项目概述&#xff1a;从一块开发板到人机交互界面的探索最近在做一个工业现场数据监控终端的原型&#xff0c;核心需求是在一块屏幕上实时显示传感器数据、设备状态&#xff0c;并且能通过触摸屏进行简单的参数设置。选型的时候&#xff0c;瑞萨电子的RA6M3 HMI Board进入了…...

3分钟学会使用PPT计时器:告别演讲超时的终极解决方案

3分钟学会使用PPT计时器&#xff1a;告别演讲超时的终极解决方案 【免费下载链接】ppttimer 一个简易的 PPT 计时器 项目地址: https://gitcode.com/gh_mirrors/pp/ppttimer 你是否在PPT演示时总是担心超时&#xff1f;是否希望有一个工具能自动帮你管理演讲时间&#x…...

即时通讯IM:从聊天工具到企业数字底座

即时通讯IM在2026年已不再只是员工桌面上用来收发消息的软件。它正经历一场深刻的角色蜕变——从“聊天工具”升级为支撑企业核心业务运转的“数字底座”。即时通讯系统已成为支撑企业核心运营的关键基础设施&#xff0c;IM正在被赋予连接一切、打通信息流的关键角色。 这种进化…...

昇腾CANN shmem:把多张 NPU 的 HBM 变成一块全局内存

hccl 的通信模型是消息传递——发送方调 send&#xff0c;接收方调 recv&#xff0c;两边同步。hixl 的模型是单边推送——发送方调 put&#xff0c;接收方不用参与。shmem 是第三种模型&#xff1a;PGAS&#xff08;Partitioned Global Address Space&#xff09;&#xff0c;…...

超强干货整理!2026GEO排名查询监测系统排名,适配多场景企业需求

2026年&#xff0c;AI搜索主导信息分发逻辑&#xff0c;GEO&#xff08;生成式引擎优化&#xff09;成为企业品牌曝光、流量增长的核心抓手。对企业而言&#xff0c;GEO优化的关键不仅是“铺内容、做适配”&#xff0c;更在于“精准监测、科学优化”——唯有实时掌握AI搜索排名…...

从‘管理模式’到‘监听模式’:一张无线网卡在Kali Linux下的四种工作模式详解与切换实战

从‘管理模式’到‘监听模式’&#xff1a;一张无线网卡在Kali Linux下的四种工作模式详解与切换实战 当你第一次在Kali Linux中插入无线网卡时&#xff0c;它默认处于"管理模式"——就像普通笔记本电脑连接WiFi一样温顺。但在这张小小的硬件里&#xff0c;其实藏着四…...

别再只盯着IoU了!深入浅出聊聊边界框回归:从IoU到Shape-IoU的演进与选择

边界框回归的进化论&#xff1a;从IoU到Shape-IoU的技术跃迁与实战选型 当我们在计算机视觉领域谈论目标检测时&#xff0c;边界框回归就像是一场永不停歇的进化竞赛。从最初的IoU开始&#xff0c;这场竞赛已经经历了GIoU、DIoU、CIoU、SIoU等多个技术迭代&#xff0c;而最新登…...