协程析构时资源未释放完易导致 use-after-free,应避免在析构函数中直接释放资源,改用 std::shared_ptr 管理生命周期,确保异步任务与协程帧共享资源所有权,并在 cancel 回调或 stop_callback 中主动终止 io。

协程对象析构时资源还在用,怎么办
协程挂起后,对象可能被销毁,但后台任务(比如网络读写、文件IO)还在跑——这时 ~MyCoroutine() 被调用,但资源(如 std::unique_ptr<handle></handle>、uv_tcp_t* 或自定义 buffer)还没真正释放完,容易触发 double-free 或 use-after-free。
根本原因不是“析构太慢”,而是协程生命周期和资源生命周期错位:C++ 对象析构是同步的,而协程恢复/取消是异步的。
- 别在析构函数里直接
close()或delete资源;改用引用计数或移交所有权给异步任务 - 用
std::shared_ptr包裹资源,让协程帧(frame)和异步回调共同持有;析构只减引用,不立即释放 - 对基于 libuv / asio 的场景,确保
uv_close()后不再访问 handle,且 close 回调里才真正 delete 对象 - 如果用
co_await自定义 awaiter,它的await_suspend()返回std::coroutine_handle时,要确认该 handle 持有资源强引用
std::coroutine_handle 析构不等于协程结束
std::coroutine_handle::destroy() 只销毁协程帧内存,不保证协程已执行完或已取消。若帧里存了裸指针或非共享资源,destroy() 后再 resume 就崩。
典型错误:把 this 指针塞进协程帧,然后在析构函数里调 handle.destroy() —— 此时协程可能正卡在 co_await socket.read(),后续 resume 会访问已析构对象。
- 用
std::shared_from_this()替代裸this,确保协程帧里存的是std::shared_ptr<myclass></myclass> - 手动取消协程前,先调
handle.promise().cancel()(需 promise 类型支持),再等 pending 状态清空 - libuv 用户注意:
uv_cancel()不是万能的,TCP read/write 的 cancel 行为依赖底层实现,有些平台根本不支持
async_cleanup 不能靠 co_return 后的代码
很多人以为在协程末尾写 co_await cleanup(); 就万事大吉——但用户可能中途 co_await std::stop_token 或抛异常,甚至直接丢弃 std::coroutine_handle,导致那段清理代码根本没机会执行。
协程的“自然结束”路径不可靠,尤其在组合多个协程(when_all、with_timeout)时更明显。
- 把关键清理逻辑提前到 promise 构造阶段,例如在
promise_type::promise_type()里初始化std::shared_ptr<resource></resource> - 用 RAII wrapper:写个
AsyncResourceGuard,构造时注册 on-cancel 回调,析构时仅触发取消,不阻塞 - 避免在
await_resume()里释放资源;它可能被多次调用(比如重试逻辑),应只负责“取结果”,不负责“收尾”
std::stop_token 取消后资源仍可能活跃
co_await stop_token 只中断协程挂起点,不自动关闭 socket、cancel timer、释放 buffer。停止请求发出后,你得自己检查状态并主动终止异步操作。
常见表现:调了 request_stop(),log 显示 “canceled”,但 Wireshark 还看到 TCP retransmit,或者 valgrind 报 buffer 泄漏。
- 在
await_suspend()中检查stop_token.stop_requested(),若已请求则跳过实际挂起,直接返回 ready - 每个异步操作(如
asio::async_read)都配一个std::stop_callback,在回调里调cancel()或close() - 不要依赖
std::jthread的自动 join:协程不是线程,jthread无法感知协程内部的 pending IO
最易被忽略的一点:协程 promise 对象本身可能被分配在栈上(比如 lambda 返回协程),此时整个生命周期由调用方栈帧控制——一旦栈展开,promise 析构,但后台 IO 还在跑。这种场景下,必须把 promise 拷贝到堆上(operator new + std::coroutine_handle::from_promise()),否则一切 RAII 都白搭。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











