c++ asio 如何维护异步对象的生命周期

问题描述 投票:0回答:1

在 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
销毁的异步对象的正确方法是什么?

c++ boost-asio
1个回答
0
投票

在 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()
© www.soinside.com 2019 - 2024. All rights reserved.