协程状态机的核心是“状态转移控制权”而非“挂起”,需自定义promise_type接管流转逻辑,通过final_suspend返回suspend_always防止销毁、unhandled_exception处理异常、next_state驱动跳转,并由驱动循环显式resume推进状态。

协程状态机的核心不是“挂起”,而是“状态转移控制权”
用 C++20 协程写状态机,关键不在 co_await 本身,而在如何把每个状态封装成可恢复的执行片段,并让状态跳转由协程的恢复点(resume point)自然承载。标准库不提供现成的状态机协程框架,必须自己设计 promise_type 来接管状态流转逻辑。
必须自定义 promise_type 并重载 unhandled_exception 和 final_suspend
默认的 std::coroutine_handle 无法支撑状态机语义——你不能让协程在状态结束时自动销毁(否则无法做状态迁移),也不能任由异常崩溃掉整个状态流。需要:
-
final_suspend返回std::suspend_always{},防止协程彻底销毁,保留 handle 供外部驱动下个状态 -
unhandled_exception至少记录或转发异常,避免静默终止(状态机里异常常意味着非法转移) - 在
promise_type中添加next_state成员(如std::function或枚举),并在await_resume()后主动调用它
struct state_machine_promise {
std::function next_state;
auto get_return_object() { return state_machine{this}; }
auto initial_suspend() { return std::suspend_always{}; }
auto final_suspend() noexcept { return std::suspend_always{}; }
void unhandled_exception() { /* 记录异常,不抛出 */ }
void return_void() {}
auto await_transform(auto&&) { return simple_awaiter{*this}; }
}; 用 awaiter 控制状态进入与退出时机
每个状态对应一个 co_await 表达式,但它的 awaiter 不该只做“等待”,而应完成三件事:保存当前状态上下文、触发退出逻辑(如清理资源)、设置 next_state。常见错误是把所有状态逻辑塞进一个协程函数里,结果变成难以调试的“大面条”。
- 每个状态建议拆成独立函数,返回
state_machine类型(包装了coroutine_handle) -
simple_awaiter::await_ready()应始终返回false,确保每次co_await都真实挂起,把控制权交还给驱动循环 - 不要依赖
co_yield实现状态跳转——它语义是“产出值并挂起”,和状态机的“执行→转移”模型不匹配
驱动循环必须显式 resume 并检查状态终止
协程状态机没有内置调度器,你得自己写一个运行循环来推进状态。容易忽略的是:不能无条件 handle.resume(),必须先检查 handle.done(),否则对已结束协程 resume 会触发未定义行为(UB)。
立即学习“C++免费学习笔记(深入)”;
void run_state_machine(state_machine sm) {
auto h = sm.handle;
while (!h.done()) {
h.resume();
// 此处可插入日志、超时检查、事件分发等
}
}真正复杂的地方在于状态间的数据传递——promise_type 是唯一共享上下文的位置,所有状态共用同一份 promise 实例,变量生命周期必须严格匹配整个状态机周期,局部变量不能跨 co_await 存活(除非用 static 或堆分配)。











