在 asio 中,每当我想使用异步对象来执行某些异步操作时,我都必须使用共享指针,因为异步操作处理程序位于 io_context 内部,并且如果异步对象被销毁,仍然可以调用处理程序。
#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
但是如果我希望 async_operation 成为一个协程怎么办?我是否必须将共享指针作为参数传递? Session的界面不可以是这样吗?
struct Session {
auto connect(std::string_view host, std::string_view port) -> asio::awaitable<void>;
};
我可以使用 lambda 协程,或者我可以像这样转发 async_operation 协程的实现:
#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
销毁的异步对象的正确方法是什么?
在 asio 中,每当我想使用异步对象来执行某些异步操作时,我都必须使用共享指针,因为异步操作处理程序位于 io_context 内部,并且如果异步对象被销毁,仍然可以调用处理程序。
取决于您对“异步对象”的定义(这不是一个明确定义的术语)。请记住,您可以进行仅移动操作,这就是经常使用
unique_ptr
的原因。事实上,有些操作根本不需要引用的稳定性,您只需移动整个处理程序对象即可。
尽管如此,共享所有权通常是最简单的,也很常见。
但是如果我希望 async_operation 成为一个协程怎么办?我必须将共享指针作为参数传递吗?
实际上差别很小。
async_operation
也没有获取会话对象。您通常所做的是将操作的整个状态共享:
struct Session : public std::enable_shared_from_this<Session> {
~Session() {
stopped = true;
std::cout << "~Session()" << std::endl;
}
Session(asio::any_io_executor ex) : t{ex} {}
asio::steady_timer t;
std::atomic_bool stopped = false;
void async_operation() {
t.expires_after(1s);
t.async_wait([this, self = shared_from_this()](error_code error) {
std::cout << error.value() << ' ' << error.message() << std::endl;
std::cout << "stopped: " << stopped << std::endl;
});
}
};
注意您如何捕获
self = shared_from_this()
以与所有相关成员共享 Session
的所有权。
Session的界面不可以是这样吗?
当然。它会是相同的,但稍微简单一些。 coro 框架包括堆栈,因此您可以“仅”在 coro 内部捕获:
struct CoSession : public std::enable_shared_from_this<CoSession> {
~CoSession() {
stopped = true;
std::cout << "~CoSession()" << std::endl;
}
std::atomic_bool stopped = false;
asio::awaitable<void> async_operation() {
auto self = shared_from_this();
asio::steady_timer t{co_await asio::this_coro::executor, 1s};
auto [error] = co_await t.async_wait(asio::as_tuple(asio::deferred));
std::cout << error.message() << std::endl;
std::cout << "stopped: " << stopped << std::endl;
}
};
然后您可以使用:
asio::io_context ctx;
auto s = std::make_shared<CoSession>();
asio::co_spawn(ctx, s->async_operation(), asio::detached);
如果你想避免“棘手”的自捕获,你还可以将session对象绑定到coro中:
asio::co_spawn(ctx, std::bind(&CoSession::async_operation, s), asio::detached);
// or even
asio::co_spawn(ctx, std::bind(&CoSession::async_operation, std::move(s)), asio::detached);
如果您意识到 coro 堆栈是 coro 状态的一部分并且适用正常的 C++ 对象生存期...您可以看到更自然的模式,避免共享生存期:
asio::awaitable<void> run_session() {
SimpleSession local;
co_await local.async_operation();
}
注意:如果在那里将
更改为co_await
,它会再次出现终身问题,因为 在这种情况下return
在async_operation
被破坏之前不会完成。local
并排演示所有方法:
#include <boost/asio.hpp>
#include <iostream>
using namespace std::chrono_literals;
using boost::system::error_code; // non-standalone asio doens't declare error_code
namespace asio = boost::asio;
struct Session : public std::enable_shared_from_this<Session> {
~Session() {
stopped = true;
std::cout << "~Session()" << std::endl;
}
Session(asio::any_io_executor ex) : t{ex} {}
asio::steady_timer t;
std::atomic_bool stopped = false;
void async_operation(unsigned line) {
t.expires_after(1s);
t.async_wait([this, self = shared_from_this(), line](error_code error) {
std::cout << line << ": " << error.message() << std::endl;
std::cout << line << ": stopped = " << stopped << std::endl;
});
}
};
struct CoSession : public std::enable_shared_from_this<CoSession> {
~CoSession() {
stopped = true;
std::cout << "~CoSession()" << std::endl;
}
std::atomic_bool stopped = false;
asio::awaitable<void> async_operation(unsigned line) {
auto self = shared_from_this();
asio::steady_timer t{co_await asio::this_coro::executor, 1s};
auto [error] = co_await t.async_wait(asio::as_tuple(asio::deferred));
std::cout << line << ": " << error.message() << std::endl;
std::cout << line << ": stopped = " << stopped << std::endl;
}
};
struct SimpleSession {
~SimpleSession() {
stopped = true;
std::cout << "~SimpleSession()" << std::endl;
}
std::atomic_bool stopped = false;
asio::awaitable<void> async_operation(unsigned line) {
asio::steady_timer t{co_await asio::this_coro::executor, 1s};
auto [error] = co_await t.async_wait(asio::as_tuple(asio::deferred));
std::cout << line << ": " << error.message() << std::endl;
std::cout << line << ": stopped = " << stopped << std::endl;
}
};
asio::awaitable<void> run_session(unsigned line) {
SimpleSession local;
co_await local.async_operation(line);
}
int main() {
std::cout << std::boolalpha;
auto delay1 = 100ms;
auto delay2 = 2s;
{
std::cout << " --- Session\n";
asio::io_context ctx;
auto s = std::make_shared<Session>(ctx.get_executor());
s->async_operation(__LINE__);
ctx.run_for(delay1);
std::cout << __LINE__ << ": use_count: " << s.use_count() << std::endl;
s.reset();
ctx.run_for(delay2);
}
{
std::cout << " --- CoSession, unbound\n";
asio::io_context ctx;
auto s = std::make_shared<CoSession>();
asio::co_spawn(ctx, s->async_operation(__LINE__), asio::detached);
ctx.run_for(delay1);
std::cout << __LINE__ << ": use_count: " << s.use_count() << std::endl;
s.reset();
ctx.run_for(delay2);
}
{
std::cout << " --- CoSession, bound\n";
asio::io_context ctx;
auto s = std::make_shared<CoSession>();
asio::co_spawn(ctx, std::bind(&CoSession::async_operation, s, __LINE__), asio::detached);
ctx.run_for(delay1);
std::cout << __LINE__ << ": use_count: " << s.use_count() << std::endl;
s.reset();
ctx.run_for(delay2);
}
{
std::cout << " --- CoSession, move-bound\n";
asio::io_context ctx;
auto s = std::make_shared<CoSession>();
asio::co_spawn(ctx, std::bind(&CoSession::async_operation, std::move(s), __LINE__), asio::detached);
ctx.run_for(delay1);
std::cout << __LINE__ << ": use_count: " << s.use_count() << std::endl;
s.reset();
ctx.run_for(delay2);
}
{
std::cout << " --- SimpleSession, Coro lifetime\n";
asio::io_context ctx;
asio::co_spawn(ctx, run_session(__LINE__), asio::detached);
ctx.run_for(delay2);
}
}
打印例如
--- Session
78: use_count: 2
76: Success
76: stopped = false
~Session()
--- CoSession, unbound
90: use_count: 2
88: Success
88: stopped = false
~CoSession()
--- CoSession, bound
101: use_count: 3
99: Success
99: stopped = false
~CoSession()
--- CoSession, move-bound
112: use_count: 0
110: Success
110: stopped = false
~CoSession()
--- SimpleSession, Coro lifetime
120: Success
120: stopped = false
~SimpleSession()