C++ Asio异步对象生命周期维护:栈对象独立销毁的优雅方案
Asio中栈上异步对象的安全销毁方案
在Asio中使用执行异步操作的对象时,传统回调式异步操作必须用shared_ptr管理对象生命周期——因为异步操作的handler会驻留在io_context中,即便对象本身被销毁,handler仍可能被调用,导致访问已释放的内存。
传统回调模式的问题示例
#include <bits/stdc++.h> #include <asio.hpp> using namespace std; asio::io_context ctx; struct Session { ~Session() { *stopped = true; cout << "~Session()" << endl; } std::shared_ptr<bool> stopped = std::make_shared<bool>(false); asio::steady_timer t{ctx}; void async_operation() { t.expires_after(1s); t.async_wait([this, stopped = stopped](const asio::error_code &error) { cout << error.value() << ' ' << error.message() << endl; cout << "*stopped: " << *stopped << endl; }); } }; int main() { cout << boolalpha; { Session s; s.async_operation(); ctx.run_for(100ms); cout << "use_count: " << s.stopped.use_count() << endl; } ctx.run_for(2s); }
输出:
use_count: 2 ~Session() 125 Operation aborted. *stopped: true
协程场景的疑问
如果把异步操作改成协程形式,是否必须传递shared_ptr?能否设计成更简洁的接口,比如:
struct Session { auto connect(std::string_view host, std::string_view port) -> asio::awaitable<void>; };
用户尝试通过转发shared_ptr实现协程版操作,但方案略显繁琐:
#include <bits/stdc++.h> #include <asio.hpp> using namespace std; inline constexpr auto use_tuple_awaitable = asio::as_tuple(asio::use_awaitable); asio::io_context ctx; struct Session { ~Session() { *stopped = true; cout << "~Session" << endl; } std::shared_ptr<bool> stopped = std::make_shared<bool>(false); asio::steady_timer t{ctx}; auto async_operation_impl(std::shared_ptr<bool> stopped_) -> asio::awaitable<void> { t.expires_after(1s); auto &&[error] = co_await t.async_wait(use_tuple_awaitable); cout << error.value() << ' ' << error.message() << endl; cout << "*stopped: " << *stopped_ << endl; } auto async_operation() -> asio::awaitable<void> { return async_operation_impl(stopped); } }; int main() { cout << boolalpha; { Session s; asio::co_spawn(asio::system_executor(), s.async_operation(), asio::detached); cout << "use_count: " << s.stopped.use_count() << endl; } ctx.run_for(2s); }
输出:
use_count: 2 ~Session() 125 Operation aborted. *stopped: true
正确实现方式:协程生命周期绑定+主动取消
要实现栈上生存、可独立于io_context销毁的异步对象,核心是让协程的生命周期与对象绑定,并在对象销毁时主动取消所有未完成的异步操作,无需额外传递shared_ptr。
优化后的协程版实现
#include <bits/stdc++.h> #include <asio.hpp> using namespace std; inline constexpr auto use_tuple_awaitable = asio::as_tuple(asio::use_awaitable); asio::io_context ctx; struct Session { ~Session() { cout << "~Session" << endl; // 对象销毁时主动取消定时器 t.cancel(); } asio::steady_timer t{ctx}; auto async_operation() -> asio::awaitable<void> { t.expires_after(1s); auto &&[error] = co_await t.async_wait(use_tuple_awaitable); // 仅在未被取消时处理结果 if (!error || error != asio::error::operation_aborted) { cout << error.value() << ' ' << error.message() << endl; } else { cout << "异步操作已被取消" << endl; } } }; int main() { cout << boolalpha; { Session s; // 协程绑定到Session对象的生命周期,对象销毁时协程会收到取消信号 asio::co_spawn(ctx.get_executor(), [&s]() -> asio::awaitable<void> { co_await s.async_operation(); }, asio::detached); ctx.run_for(100ms); } ctx.run_for(2s); }
输出:
~Session 异步操作已被取消
关键要点
- 主动取消异步操作:在对象析构函数中调用异步操作对象(如
steady_timer)的cancel()方法,Asio会立即触发协程的完成回调,并返回operation_aborted错误码。 - 协程与对象生命周期绑定:通过lambda捕获栈上对象的引用,当对象被销毁时,异步操作被取消,协程会收到错误并自然结束,不会访问已释放的对象内存。
- 无需shared_ptr:只要保证在协程执行期间对象未被销毁(或销毁时主动取消),就可以安全使用栈上对象,避免智能指针的繁琐。
扩展:多异步操作的统一管理
如果对象包含多个异步操作(如socket、定时器等),可以将所有异步操作对象统一管理,在析构时批量调用cancel(),确保所有未完成的操作都被终止,避免悬空引用。
内容的提问来源于stack exchange,提问作者Tellegar
相关产品推荐
相关产品推荐

