如何在Boost.Asio+C++20协程中实现RAII式可靠清理?
Boost.Asio C++20协程的可靠RAII式清理实现
背景与现有尝试
手动清理的可行版本(非RAII)
通过在协程中编写try-catch确保清理逻辑始终执行,这是最直接的方式,但需要重复编写异常处理代码:
#include <iostream> #include <boost/asio.hpp> namespace asio = boost::asio; asio::awaitable<void> test_setup() { std::cout << "setup" << std::endl; co_return; } asio::awaitable<void> test_body() { std::cout << "body" << std::endl; throw 1; // 模拟测试代码抛出异常 co_return; } asio::awaitable<void> test_cleanup() { std::cout << "cleanup" << std::endl; co_return; } asio::awaitable<void> test_launcher() { co_await test_setup(); try { co_await test_body(); } catch (...) { std::cout << "caught exception" << std::endl; } // 始终执行清理 co_await test_cleanup(); co_return; } int main() { asio::io_context ioc; asio::co_spawn( ioc.get_executor(), test_launcher, asio::detached ); ioc.run(); }
输出:
setup body caught exception cleanup
移除try-catch的尝试(存在缺陷)
尝试将异常处理移到co_spawn的完成回调中,但这样会导致test_body抛出异常时,test_cleanup无法执行:
asio::co_spawn( ioc.get_executor(), test_launcher, [](std::exception_ptr ep) { if (ep) { try { std::rethrow_exception(ep); } catch (...) { std::cout << "caught exception" << std::endl; } } } );
作用域守卫临时方案(仍有不足)
用带自定义删除器的shared_ptr实现RAII,但删除器无法直接co_await,只能启动detached清理协程,导致无法等待清理完成:
asio::awaitable<void> test_launcher() { co_await test_setup(); auto exe = co_await asio::this_coro::executor; std::shared_ptr<void> scope_guard{ nullptr, [exe](void*) { std::cout << "scope_guard called" << std::endl; asio::co_spawn( exe, test_cleanup, asio::detached ); } }; co_await test_body(); // 抛出异常 co_return; }
输出:
setup body scope_guard called cleanup begin caught exception cleanup end
当前方案的问题:
- 无法在作用域守卫中直接等待清理协程完成
- detached的清理协程可能在主协程结束后仍在运行,导致资源释放时机不可控
推荐解决方案
方案1:通用with_finally封装(最简方案)
封装一个通用的with_finally函数,接受主逻辑和清理逻辑的awaitable,确保无论主逻辑是否抛出异常,清理逻辑都会被执行并等待完成:
#include <iostream> #include <boost/asio.hpp> namespace asio = boost::asio; asio::awaitable<void> test_setup() { std::cout << "setup" << std::endl; co_return; } asio::awaitable<void> test_body() { std::cout << "body" << std::endl; throw 1; co_return; } asio::awaitable<void> test_cleanup() { std::cout << "cleanup begin" << std::endl; // 模拟耗时清理操作 co_await asio::steady_timer(co_await asio::this_coro::executor, std::chrono::milliseconds(100)).async_wait(asio::use_awaitable); std::cout << "cleanup end" << std::endl; co_return; } // 通用finally封装:确保清理逻辑始终执行 template<typename MainAwaitable, typename CleanupAwaitable> asio::awaitable<void> with_finally(MainAwaitable main_op, CleanupAwaitable cleanup_op) { try { co_await std::move(main_op); } catch (...) { // 执行清理后重新抛出异常,让co_spawn回调处理 co_await std::move(cleanup_op); throw; } // 正常退出时执行清理 co_await std::move(cleanup_op); } asio::awaitable<void> test_launcher() { co_await test_setup(); // 用with_finally包裹主逻辑和清理逻辑 co_await with_finally(test_body(), test_cleanup()); } int main() { asio::io_context ioc; asio::co_spawn( ioc.get_executor(), test_launcher, [](std::exception_ptr ep) { if (ep) { try { std::rethrow_exception(ep); } catch (int e) { std::cout << "caught exception: " << e << std::endl; } catch (...) { std::cout << "caught unknown exception" << std::endl; } } } ); ioc.run(); }
输出:
setup body cleanup begin cleanup end caught exception: 1
这个方案完全避免了重复编写try-catch,同时保证清理逻辑被等待完成,异常也能正确传递到co_spawn的回调中处理。
方案2:自定义可等待作用域守卫(灵活场景)
如果需要更灵活的清理时机控制(比如提前触发清理),可以自定义一个支持co_await的作用域守卫:
#include <iostream> #include <boost/asio.hpp> #include <exception> namespace asio = boost::asio; asio::awaitable<void> test_setup() { std::cout << "setup" << std::endl; co_return; } asio::awaitable<void> test_body() { std::cout << "body" << std::endl; throw 1; co_return; } asio::awaitable<void> test_cleanup() { std::cout << "cleanup begin" << std::endl; co_await asio::steady_timer(co_await asio::this_coro::executor, std::chrono::milliseconds(100)).async_wait(asio::use_awaitable); std::cout << "cleanup end" << std::endl; co_return; } // 可等待的作用域守卫 template<typename CleanupFunc> class awaitable_guard { public: explicit awaitable_guard(CleanupFunc cleanup) : cleanup_(std::move(cleanup)), executor_(co_await asio::this_coro::executor) {} // 析构时触发清理(若未手动执行) ~awaitable_guard() { if (!completed_) { // 若未手动等待清理,启动detached协程确保执行(但推荐手动co_await) asio::co_spawn(executor_, cleanup_, [](std::exception_ptr) {}); } } // 手动触发并等待清理完成 asio::awaitable<void> execute() { if (!completed_) { completed_ = true; co_await cleanup_(); } } private: CleanupFunc cleanup_; asio::any_io_executor executor_; bool completed_ = false; }; // 辅助函数创建守卫 template<typename CleanupFunc> asio::awaitable<awaitable_guard<CleanupFunc>> make_awaitable_guard(CleanupFunc cleanup) { co_return awaitable_guard<CleanupFunc>(std::move(cleanup)); } asio::awaitable<void> test_launcher() { co_await test_setup(); auto guard = co_await make_awaitable_guard([]() { return test_cleanup(); }); try { co_await test_body(); } catch (...) { // 异常时先等待清理完成,再抛出 co_await guard.execute(); throw; } // 正常退出等待清理 co_await guard.execute(); } int main() { asio::io_context ioc; asio::co_spawn( ioc.get_executor(), test_launcher, [](std::exception_ptr ep) { if (ep) { try { std::rethrow_exception(ep); } catch (int e) { std::cout << "caught exception: " << e << std::endl; } catch (...) { std::cout << "caught unknown exception" << std::endl; } } } ); ioc.run(); }
这个方案适合需要在不同分支灵活控制清理时机的场景,同时保证清理逻辑的可靠性。
方案3:利用Boost.Asio的deferred链式组合
通过asio::deferred将操作组合,利用协程的异常传播特性确保清理执行:
#include <iostream> #include <boost/asio.hpp> #include <boost/asio/deferred.hpp> namespace asio = boost::asio; asio::awaitable<void> test_setup() { std::cout << "setup" << std::endl; co_return; } asio::awaitable<void> test_body() { std::cout << "body" << std::endl; throw 1; co_return; } asio::awaitable<void> test_cleanup() { std::cout << "cleanup begin" << std::endl; co_await asio::steady_timer(co_await asio::this_coro::executor, std::chrono::milliseconds(100)).async_wait(asio::use_awaitable); std::cout << "cleanup end" << std::endl; co_return; } asio::awaitable<void> test_chain() { co_await test_setup(); // 用deferred包装清理操作,确保异常时也能执行 auto cleanup_op = asio::deferred([]() { return test_cleanup(); }); try { co_await test_body(); } catch (...) { co_await cleanup_op; throw; } co_await cleanup_op; } int main() { asio::io_context ioc; asio::co_spawn( ioc.get_executor(), test_chain, [](std::exception_ptr ep) { if (ep) { try { std::rethrow_exception(ep); } catch (int e) { std::cout << "caught exception: " << e << std::endl; } catch (...) { std::cout << "caught unknown exception" << std::endl; } } } ); ioc.run(); }
这个方案利用Boost.Asio的异步操作组合特性,适合已有异步操作链的场景。
关键注意事项
- 永远避免在清理逻辑中使用
detached协程而不等待完成,否则可能导致io_context提前退出或资源泄漏 - 异常需要正确传递:清理完成后重新抛出异常,确保
co_spawn的回调能处理异常 - 通用封装(如
with_finally)能最大程度减少重复代码,提升代码可维护性
内容的提问来源于stack exchange,提问作者Takatoshi Kondo
相关产品推荐
相关产品推荐

