如何将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()抛出,逻辑保持一致
关键细节
- 包装函数
make_task在创建协程后,立即调用一次resume()让协程执行到第一个挂起点 - 执行后检查协程promise中保存的异常,若存在则直接重抛,调用方用常规
try/catch即可捕获 - 若协程在启动阶段直接完成(无挂起点),也能自动处理该阶段的异常
内容的提问来源于stack exchange,提问作者glades
相关产品推荐
相关产品推荐

