You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
}

这里的问题在于:

  1. 当当前协程(比如suspend_five)挂起时,你直接resume了被await的task(比如suspend_two),但没有保存当前协程的句柄(handle参数)。
  2. 返回!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() {}
    };
};

关键修正点说明

  1. 添加m_continuation:在task_promise中保存等待当前task的协程句柄,确保被await的task完成后能回到原协程。
  2. 修正await_suspend:不再返回布尔值,而是直接设置续体并启动被await的协程。返回void意味着标准库不会自动恢复当前协程,而是由我们在final_suspend中处理。
  3. 自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.07 15:48:13