当前位置: 首页 > 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)个盒子排成一排,每个盒子都放…...

UI-TARS-desktop场景拓展:在客服、运维、办公中的实际应用

UI-TARS-desktop场景拓展&#xff1a;在客服、运维、办公中的实际应用 你是否遇到过这样的场景&#xff1a;客服团队每天重复回答相同问题&#xff0c;运维人员需要手动执行大量重复性操作&#xff0c;办公人员花费大量时间处理文档和数据&#xff1f;UI-TARS-desktop作为一款…...

interactive-deep-colorization与Adobe Photoshop Elements对比分析:免费AI上色工具如何超越专业软件?

interactive-deep-colorization与Adobe Photoshop Elements对比分析&#xff1a;免费AI上色工具如何超越专业软件&#xff1f; 【免费下载链接】interactive-deep-colorization Deep learning software for colorizing black and white images with a few clicks. 项目地址: …...

任务追踪智能体(二)

个人任务简介 负责项目核心业务功能开发&#xff0c;包括 AI 智能评分、文件预览、数据导出等关键功能。这些功能是整个项目的价值核心&#xff0c;直接面向用户使用场景&#xff0c;实现对项目申报书的自动化AI评分。将申报文件转换为前端可预览的格式&#xff0c;支持 PDF 原…...

轻松掌握gallery多渠道打包:为不同应用商店构建专属本地AI平台版本

轻松掌握gallery多渠道打包&#xff1a;为不同应用商店构建专属本地AI平台版本 【免费下载链接】gallery A gallery that showcases on-device ML/GenAI use cases and allows people to try and use models locally. 项目地址: https://gitcode.com/GitHub_Trending/gallery…...

课堂学习1

Miniconda 安装教程 (2026版) Anaconda 是最流行的 Python 和 R 语言数据科学平台&#xff0c;它包含了康达包管理器&#xff08;Conda&#xff09;、Python 以及 1500 个科学包及其依赖项。&#xfeff;Miniconda 可以看作是 Anaconda 的“轻装版”&#xff0c;只自带 conda …...

STM8S001单片机:8引脚高性价比嵌入式开发方案

1. STM8S001单片机&#xff1a;小身材大能量的性价比之王 在嵌入式开发领域&#xff0c;我们常常陷入一个两难选择&#xff1a;要么使用功能强大但引脚众多、价格昂贵的高端MCU&#xff0c;要么选择功能简陋、开发环境不友好的廉价芯片。STMicroelectronics最新推出的STM8S001系…...

SEO_资深运营揭秘,长期稳定排名的SEO策略介绍

SEO策略的核心要素&#xff1a;内容质量 在资深运营者的经验中&#xff0c;内容质量始终是SEO策略的核心要素。一个优质的网站&#xff0c;首先需要提供高质量、有价值的内容&#xff0c;这不仅能吸引用户&#xff0c;还能提升网站在搜索引擎中的排名。长期稳定的SEO排名离不开…...

ZYNQ AXI_DMA配置避坑指南:如何避免DDR3数据传输中的栈区溢出

ZYNQ AXI_DMA配置避坑指南&#xff1a;如何避免DDR3数据传输中的栈区溢出 在嵌入式系统开发中&#xff0c;内存管理往往是决定项目成败的关键因素之一。最近接手一个ZYNQ项目时&#xff0c;我遇到了一个令人头疼的问题&#xff1a;当使用AXI_DMA从PL端向PS端的DDR3内存传输大量…...

二十载面香溢加州:鲁味居(101 Noodle Express)的北美餐饮进阶启示录

近日&#xff0c;南加州知名中餐地标品牌“鲁味居&#xff08;101 Noodle Express&#xff09;”正式迎来创立二十周年。在竞争激烈的北美餐饮市场&#xff0c;该品牌凭借对中国纯正传统面食与非遗卤味技艺的坚守&#xff0c;不仅确立了其在海外华人圈层的核心地位&#xff0c;…...

OpenClaw自动化监控:百川2-13B-4bits量化模型驱动的异常检测

OpenClaw自动化监控&#xff1a;百川2-13B-4bits量化模型驱动的异常检测 1. 为什么选择OpenClaw做自动化监控&#xff1f; 去年我负责的一个个人项目遇到了运维难题——每天需要手动检查服务器状态、扫描日志关键词、生成异常报告。这种重复性工作不仅耗时&#xff0c;还经常…...