C++20嵌套协程挂起异常:无法执行指定代码行问题咨询
问题分析与解决方案
你遇到的核心问题是自定义task类型的await_suspend逻辑错误,导致嵌套协程的执行链断裂。当你在协程中co_await另一个task时,当前协程挂起后没有被正确记录,导致被await的协程完成后,无法回到原协程继续执行后续代码——这就是suspend_five里的suspend_five -> suspend_two #3永远无法执行的原因。
错误点拆解
你的task::await_suspend实现是这样的:
bool await_suspend(std::coroutine_handle<> handle) { if (!m_handle.done()) { m_handle.resume(); } return !m_handle.done(); }
这里的问题在于:
- 当当前协程(比如
suspend_five)挂起时,你直接resume了被await的task(比如suspend_two),但没有保存当前协程的句柄(handle参数)。 - 返回
!m_handle.done()意味着如果被await的task还没完成,当前协程会保持挂起,但没有任何机制在被await的task完成后恢复当前协程。
简单说,你只启动了被await的协程,但没告诉它“完成后要回到我这里继续执行”,所以嵌套到第三层时,执行链就断了。
修正后的task实现
我们需要给task_promise添加一个continuation(续体),用来保存等待当前task的协程句柄。当当前task完成时,自动恢复这个续体协程。以下是修正后的完整task结构体:
#include <iostream> #include <coroutine> #include <optional> template <typename T> struct task { struct task_promise; using promise_type = task_promise; using handle_type = std::coroutine_handle<task_promise>; mutable handle_type m_handle; task(handle_type handle) : m_handle(handle) {} task(task&& other) noexcept : m_handle(other.m_handle) { other.m_handle = nullptr; } task(const task&) = delete; task& operator=(const task&) = delete; // awaitable接口修正 bool await_ready() { return m_handle.done(); } void await_suspend(std::coroutine_handle<> continuation) { // 将当前等待的协程句柄保存到被await的task的promise中 m_handle.promise().m_continuation = continuation; // 启动被await的协程 if (!m_handle.done()) { m_handle.resume(); } } auto await_resume() { return result(); } T result() const { if (!m_handle.done()) { m_handle.resume(); } return *m_handle.promise().m_value; } // 手动步进逻辑修正:现在只resume当前task,续体由final_suspend处理 bool one_step() { if (m_handle.done()) { return false; } m_handle.resume(); return !m_handle.done(); } ~task() { if (m_handle) { m_handle.destroy(); } } struct task_promise { std::optional<T> m_value {}; // 保存等待当前task的协程句柄 std::coroutine_handle<> m_continuation = nullptr; auto initial_suspend() { return std::suspend_always{}; } // 自定义final_suspend,恢复续体协程 struct final_awaiter { bool await_ready() noexcept { return false; } std::coroutine_handle<> await_suspend(handle_type handle) noexcept { // 如果有续体,返回它让标准库恢复;否则返回noop_coroutine(不恢复任何协程) return handle.promise().m_continuation ? handle.promise().m_continuation : std::noop_coroutine(); } void await_resume() noexcept {} }; auto final_suspend() noexcept { return final_awaiter{}; } auto return_value(T t) { m_value = t; return std::suspend_always{}; } task<T> get_return_object() { return {handle_type::from_promise(*this)}; } void unhandled_exception() { std::terminate(); } void rethrow_if_unhandled_exception() {} }; };
关键修正点说明
- 添加
m_continuation:在task_promise中保存等待当前task的协程句柄,确保被await的task完成后能回到原协程。 - 修正
await_suspend:不再返回布尔值,而是直接设置续体并启动被await的协程。返回void意味着标准库不会自动恢复当前协程,而是由我们在final_suspend中处理。 - 自定义
final_awaiter:在协程完成时,自动恢复保存的续体协程,让执行链继续下去。
关于你想要的setjmp/longjmp效果
C++20协程完全支持这种深度嵌套的中断与恢复场景,但需要更灵活的awaitable类型来实现。比如你可以自定义一个jump_point类型:
- 当协程
co_await jump_point时,保存当前协程的句柄到某个全局/外部容器中,然后挂起。 - 外部代码可以根据条件,调用保存的协程句柄的
resume()方法,将控制权返还给内层协程。
这种方式不需要依赖task的链式调用,而是直接手动管理协程的挂起与恢复,完全模拟setjmp/longjmp的行为。
验证修正后的代码
将修正后的task替换你原有的代码,运行后会看到suspend_five -> suspend_two #3正常输出,整个执行链完整走完,最终main能正确返回计算结果。
内容的提问来源于stack exchange,提问作者Yankes
相关产品推荐
相关产品推荐

