0

0

c# C#异步状态机(Async State Machine)的内部工作原理

煙雲

煙雲

发布时间:2026-01-11 11:41:02

|

793人浏览过

|

来源于php中文网

原创

async/await 是编译器生成的状态机机制,通过 MoveNext() 推进、TaskAwaiter 挂起唤醒,不创建线程;其将 async 方法重写为 IAsyncStateMachine 结构体,含 state、提升的局部变量、awaiter 字段及 MoveNext() 入口方法。

c# c#异步状态机(async state machine)的内部工作原理

async/await 不是线程切换,而是编译器生成的状态机在驱动控制流暂停与恢复——它不创建新线程,也不依赖 Task.Run,核心靠 MoveNext() 推进状态、靠 TaskAwaiter 挂起和唤醒。

async 方法怎么被编译器“重写”成状态机?

你写的这段代码:

public async Task GetDataAsync()
{
    await Task.Delay(100);
    return 42;
}

在编译后,不会生成线程或后台任务,而是被 C# 编译器(csc)自动转换为一个实现了 IAsyncStateMachine 的私有结构体(Release 模式下通常是 struct,避免堆分配)。这个结构体包含:

  • state:整型字段,记录当前执行到第几个 await 点(比如 -1=未开始,0=刚过第一个 await,-2=已完成)
  • 所有局部变量(如中间值、HttpClient 实例等)都被“提升”为该结构体的字段,以跨暂停点保持上下文
  • awaiter:由 Task.GetAwaiter() 返回的 TaskAwaiter 实例,负责注册回调并检查是否已完成
  • MoveNext():唯一入口方法,按 state 值跳转到对应逻辑块,执行完当前阶段后决定是继续还是挂起

await 到底做了什么?不是“等待”,而是“注册 + 退出”

await 不是阻塞指令,也不是轮询。它本质是三步操作:

  • 调用 task.GetAwaiter().IsCompleted 快速判断任务是否已同步完成
  • 若未完成,调用 awaiter.OnCompleted(action => { /* 调用 MoveNext() */ }) 注册延续(continuation)
  • 立即返回一个未完成的 Task 给调用方,当前帧退出(不压栈、不占线程)

例如:await client.GetStringAsync(url) 触发的是底层 I/O 完成端口(Windows)或 epoll(Linux)的非阻塞通知机制,操作系统就绪后才调度回 MoveNext() —— 这中间线程可能早已去处理其他请求了。

Kimi智能助手
Kimi智能助手

超强AI写作助手,一键总结20w字长文,支持批量文档上传,多端同步内容不怕丢失。论文综述、文档速读、脚本小说创作,统统交给Kimi!实时联网搜索,给你最智能清晰的解答。

下载

为什么 ConfigureAwait(false) 能提升性能?

默认情况下,await 会捕获当前 SynchronizationContext(如 WinForms 的 UI 上下文、ASP.NET Core 的 AspNetCoreSynchronizationContext),确保恢复时回到原上下文。但多数后台逻辑(如数据访问、计算)并不需要 UI 线程或特定上下文。

  • 捕获上下文要额外保存/恢复执行环境,有开销
  • 在 ASP.NET Core 早期版本中,还可能导致死锁(尤其误用 .Result.Wait()
  • ConfigureAwait(false) 显式告诉编译器:“恢复时随便哪个线程池线程都行”,跳过上下文捕获逻辑

推荐在所有非 UI、非上下文敏感的异步方法末尾加:

await someTask.ConfigureAwait(false);

常见陷阱:你以为的“异步”,其实同步执行了

以下情况会让 await 表现得像同步调用,却毫无收益:

  • 等待一个已 CompletedTask(如 Task.FromResult(1)):直接走 IsCompleted == true 分支,MoveNext() 一路执行到底,没挂起也没调度
  • async void 方法里抛异常:无法被调用方 try/catch 捕获,只能由 SynchronizationContext.UnhandledException 处理,极易静默失败
  • 误把 CPU 密集型操作包进 async(如 await Task.Run(() => HeavyCalc())):这属于“假异步”,真正该做的是用 Task.Run 显式卸载到线程池,而非套 async/await 壳子

最隐蔽的问题是:状态机本身是值类型(struct),但如果方法里引用了闭包或 this,编译器可能被迫装箱为 class,引发不必要的 GC 压力——调试时可用 dotnet dump analyze 查看堆上是否大量存在 *StateMachine 类型实例。

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

194

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

186

2025.07.04

javascriptvoid(o)怎么解决
javascriptvoid(o)怎么解决

javascriptvoid(o)的解决办法:1、检查语法错误;2、确保正确的执行环境;3、检查其他代码的冲突;4、使用事件委托;5、使用其他绑定方式;6、检查外部资源等等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

175

2023.11.23

java中void的含义
java中void的含义

本专题整合了Java中void的相关内容,阅读专题下面的文章了解更多详细内容。

96

2025.11.27

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

386

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

568

2023.08.10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

386

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

568

2023.08.10

c++主流开发框架汇总
c++主流开发框架汇总

本专题整合了c++开发框架推荐,阅读专题下面的文章了解更多详细内容。

80

2026.01.09

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 6.9万人学习

Git 教程
Git 教程

共21课时 | 2.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号