Rust异步状态机的状态是如何确定的?
Rust 编译器确定异步状态,不是先数 await,再机械地生成几个状态。
真正的过程是:
源代码
↓
建立控制流
↓
找出所有“可能暂停”的位置
↓
把这些位置变成“恢复点”
↓
分析每个恢复点之后还需要哪些局部变量
↓
把“恢复位置 + 需要保存的数据”编码进异步函数的内部状态
所以你问的两个核心问题:
状态怎么确定?
答案:由控制流中的可暂停点,以及从这些点恢复时必须区分的执行路径决定。
状态边界在哪里?
答案:边界出现在执行可能被挂起的地方;从一个恢复点开始,到下一个可能挂起的位置或函数结束,是下一段可继续执行的代码。
但这还不够精确。下面直接拿代码推。
1. 最简单的情况:两个 await
async fn foo() {
A();
let x = B().await;
C(x);
let y = D(x).await;
E(y);
}
先不要想 Future,也不要想 poll。
只问一个问题:
执行到什么地方以后,函数可能暂时不执行了?
答案只有两个:
B().await
D(x).await
因为 A()、C(x)、E(y) 都必须执行完以后才能继续走下一步。
于是原来的连续执行:
A
↓
B().await
↓
C(x)
↓
D(x).await
↓
E(y)
被两个“可暂停点”切开:
A
↓
B().await
│
├── 已完成 → 继续
│
└── 未完成 → 暂停
↑
|
以后恢复
|
↓
C(x)
↓
D(x).await
│
├── 已完成 → 继续
│
└── 未完成 → 暂停
↑
|
以后恢复
|
↓
E(y)
所以会有三个恢复阶段:
阶段 0:
A();
推动 B
阶段 1:
得到 B 的结果
C(x);
推动 D
阶段 2:
得到 D 的结果
E(y);
完成
注意这个非常关键:
不是“两次 await,所以有两个状态”。
而是:
两个可暂停点把整个执行过程分成三个“恢复阶段”。
因此最简单情况下:
N 个独立的可暂停点
→ N + 1 个连续执行阶段
这是你要抓住的第一条规则。
2. 为什么第一次 await 前也有一个状态?
你之前一直追问这个,其实这里就能看清。
例如:
async fn foo() {
A();
B().await;
}
有人会误以为:
B().await = 状态 0
不是。
因为第一次调用这个异步函数时,程序必须知道:
我是第一次进入这个函数。
于是需要一个“起始状态”:
状态 0:
A();
推动 B
如果 B 没完成:
状态 0
↓
B 未完成
↓
保存“下一次从 B 这里继续”
↓
Pending
以后再次进入:
状态 1:
B 已完成
继续执行 await 后面的代码
所以从“恢复”的角度看:
开始执行
↓
第一次暂停
↓
第二次恢复
↓
第二次暂停
↓
第三次恢复
↓
完成
状态描述的是“现在重新进入这个异步函数时应该走哪条控制流”。
3. 真正关键:为什么“一个 await 后面”一定要成为新的恢复点?
因为 await 可能返回:
Poll::Pending
比如:
let x = B().await;
C(x);
假设第一次执行:
B → Pending
那么 C(x) 根本不能执行。
函数必须退出。
于是系统要记住:
“以后 B 完成以后,不要重新从 A 开始,
而是直接继续处理 B 的结果,然后执行 C。”
所以必须有一个新的恢复位置:
恢复位置 = await 后面
这就是为什么:
await
↓
新的恢复阶段
不是设计者随便规定,而是暂停/恢复这个机制逼出来的。
4. 现在进入真正重要的地方:if
你问的是“编译器怎么确定状态”,真正能看出答案的是分支。
async fn foo() {
if condition {
A().await;
B();
} else {
C().await;
D();
}
E();
}
控制流不是一条直线,而是:
┌→ A().await → B() ─┐
│ │
开始 → if ───┤ ├→ E
│ │
└→ C().await → D() ─┘
现在第一次暂停可能发生在:
A().await
也可能发生在:
C().await
所以恢复时必须知道:
我之前是在 A 那条路径暂停的
还是:
我之前是在 C 那条路径暂停的
因此你看:
状态数量已经不是简单地等于 await 数量。
因为这里需要区分不同的控制流路径。
概念上可能是:
状态 0:
判断 condition
状态 1:
等待 A
A 完成后执行 B
然后汇合到 E
状态 2:
等待 C
C 完成后执行 D
然后汇合到 E
状态 3:
执行 E
完成
所以真正决定状态的东西已经显现出来:
不是源代码长什么样,而是暂停以后,恢复时需要区分哪些“继续执行的位置”。
5. 所以“状态边界”的精确定义是什么?
可以给你一个最实用的定义:
状态边界,就是异步执行可能被中断并交出执行权的位置。
在 Rust 里最常见的这种位置就是:
.await
但是一个真正的状态,不是“这个 await 本身”。
而是:
从一个恢复位置开始
↓
执行若干普通代码
↓
直到遇到下一个可能暂停的位置
例如:
A();
B().await;
C();
D();
E().await;
F();
概念上:
状态 0:
A();
推动 B;
─────────→ 可能暂停
状态 1:
得到 B;
C();
D();
推动 E;
─────────→ 可能暂停
状态 2:
得到 E;
F();
完成
这就是状态边界。
6. 那普通的 if、loop、match 为什么有时候也会影响状态?
因为真正分析的不是“有没有 await”,而是控制流。
例如:
async fn foo() {
loop {
if condition {
A().await;
}
B();
}
}
这里控制流可能:
┌──────────────────────┐
↓ │
B() │
↓ │
回到 loop │
│ │
↓ │
condition │
/ \ │
/ \ │
true false │
│ │ │
↓ └──────────────┘
A().await
如果 condition == false,根本不暂停。
如果 condition == true,就在 A().await 暂停。
所以恢复时需要知道:
我现在处在哪个循环迭代?
我是不是正在等待 A?
恢复以后往哪里跳?
这就是为什么编译器最终处理的其实是:
控制流图,而不是简单地扫描源码数
await。
7. 再往深一层:状态其实就是“程序计数器的离散化”
这是理解 Rust 异步最有用的一个底层视角。
普通机器执行程序时,本质上一直存在:
下一条指令在哪里?
就是程序计数器。
异步函数的问题是:
我现在执行到这里
↓
我要退出了
↓
以后回来
↓
必须知道从哪里继续
所以编译器把大量可能的执行位置,压缩成几个恢复位置:
恢复位置 0
恢复位置 1
恢复位置 2
...
然后 Future 里面有类似:
当前恢复位置 = 1
下一次 poll:
根据恢复位置 1
↓
从对应控制流继续执行
所以你可以把“状态”理解成:
为了让一个已经暂停的异步函数以后能准确恢复,编译器保存的“下一步该往哪里走”。
8. 这时候你就能回答“状态到底怎么确定”
实际上可以分成三个步骤。
第一步:编译器先把代码变成控制流
例如:
A();
if x {
B().await;
} else {
C().await;
}
D();
变成类似:
入口
↓
A
↓
if
├── B.await ──→ D
└── C.await ──→ D
↓
结束
第二步:找出可以挂起的位置
这里:
B.await
C.await
是两个挂起位置。
第三步:给“恢复后应该从哪里继续”建立标识
比如概念上:
0 = 第一次进入
1 = 正在等待 B,B 完成后走 D
2 = 正在等待 C,C 完成后走 D
3 = 已完成
于是:
状态 0
↓
判断 x
↓
状态 1 或状态 2
↓
等待完成
↓
状态 3
这就是状态机。
9. 还有一个特别重要的修正:状态不是固定等于“代码段”
比如:
async fn foo() {
let x = bar().await;
println!("{}", x);
}
概念上我们喜欢画:
状态 0:等待 bar
状态 1:打印 x
但编译器实际并不需要真的生成:
enum State {
WaitingBar,
Printing,
}
它可以用各种内部表示,甚至经过优化以后,最终机器码和你想象的这个结构差别很大。
所以:
“状态”是语义上的恢复状态,不是说编译器源码里一定有一个
enum State。
我们画 enum 只是为了把编译器做的事情具象化。
10. 最后给你一个真正准确的判定标准
以后你自己分析任何 Rust async 代码,不要问:
“这里是不是一个代码块?”
也不要单纯问:
“这里是不是一个
await?”
只问两个问题:
问题一
这里执行过程中能不能暂时停下来,把控制权交出去?
能 → 这里可能形成挂起边界。
问题二
以后恢复时,必须从哪里继续?
把所有可能的“继续位置”区分开 → 就得到了状态。
所以:
状态
=
一个能够唯一确定“恢复后继续哪条控制流”的信息
而状态边界:
=
异步执行可能暂停的位置
对于最简单的线性代码:
N 个 await
→ N 个暂停点
→ 通常形成 N+1 个连续恢复阶段
一旦出现:
if
match
loop
多个控制流汇合
嵌套 async
就不能再靠“数 await”确定状态数,必须看整个控制流,以及每个暂停位置恢复时需要区分哪些路径。
这才是 Rust 编译器确定异步状态机状态的真正依据。