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

如何将C++协程中的所有异常完整传播回调用方?

解决协程启动阶段异常自动传播的方案

核心思路:用包装函数捕获启动阶段异常

你可以编写一个协程包装器,在调用协程函数后立即执行到第一个挂起点,同步捕获这段过程中抛出的异常并直接抛给调用方,同时保留后续resume()调用时的异常传播逻辑,无需强制让协程初始挂起。

具体实现示例(以C++20协程为例)

假设你有自定义协程返回类型Task,可以通过新增包装函数实现需求:

#include <coroutine>
#include <exception>
#include <stdexcept>
#include <cstdio>

struct Task {
    struct promise_type {
        Task get_return_object() { return Task{this}; }
        std::suspend_never initial_suspend() { return {}; } // 初始不挂起
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {
            // 保存异常,供后续resume或包装函数抛出
            exception_ = std::current_exception();
        }

        std::exception_ptr exception_;
    };

    Task(promise_type* p) : handle_(std::coroutine_handle<promise_type>::from_promise(*p)) {}
    ~Task() { if (handle_) handle_.destroy(); }

    // 禁用拷贝,支持移动
    Task(const Task&) = delete;
    Task& operator=(const Task&) = delete;
    Task(Task&& other) noexcept : handle_(other.handle_) { other.handle_ = nullptr; }
    Task& operator=(Task&& other) noexcept {
        if (this != &other) {
            if (handle_) handle_.destroy();
            handle_ = other.handle_;
            other.handle_ = nullptr;
        }
        return *this;
    }

    void resume() {
        if (!handle_.done()) {
            handle_.resume();
            // 续行后检查并抛出异常
            if (handle_.promise().exception_) {
                std::rethrow_exception(handle_.promise().exception_);
            }
        }
    }

    // 暴露内部句柄供包装函数使用(也可以设计为友元)
    std::coroutine_handle<promise_type> get_handle() const { return handle_; }

private:
    std::coroutine_handle<promise_type> handle_;
};

// 包装函数:自动执行到第一个挂起点并同步抛出启动阶段异常
template<typename Func, typename... Args>
auto make_task(Func&& func, Args&&... args) {
    auto task = std::forward<Func>(func)(std::forward<Args>(args)...);
    auto handle = task.get_handle();
    
    if (!handle.done()) {
        // 执行一次resume,走到第一个挂起点
        handle.resume();
        // 检查启动阶段是否抛出异常,有则直接重抛
        if (handle.promise().exception_) {
            std::rethrow_exception(handle.promise().exception_);
        }
    }
    return task;
}

// 示例协程函数
Task test_coroutine(bool throw_on_start) {
    if (throw_on_start) {
        throw std::runtime_error("启动阶段抛出异常");
    }
    co_await std::suspend_always{};
    throw std::runtime_error("挂起后续行抛出异常");
}

// 使用示例
int main() {
    try {
        // 启动阶段异常会被包装函数同步抛出,直接被捕获
        auto task = make_task(test_coroutine, true);
    } catch (const std::exception& e) {
        printf("捕获异常:%s\n", e.what());
    }

    try {
        auto task = make_task(test_coroutine, false);
        task.resume(); // 续行时抛出挂起后的异常
    } catch (const std::exception& e) {
        printf("捕获异常:%s\n", e.what());
    }
    return 0;
}

方案优势

  • 无需修改原有协程函数逻辑,保留协程的参数传递能力,不用强制初始挂起的繁琐设计
  • 启动阶段的异常直接同步抛给调用方,无需手动额外处理
  • 后续挂起续行时的异常依然通过resume()抛出,逻辑保持一致

关键细节

  1. 包装函数make_task在创建协程后,立即调用一次resume()让协程执行到第一个挂起点
  2. 执行后检查协程promise中保存的异常,若存在则直接重抛,调用方用常规try/catch即可捕获
  3. 若协程在启动阶段直接完成(无挂起点),也能自动处理该阶段的异常

内容的提问来源于stack exchange,提问作者glades

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 02:25:19