对同一链中的 boost::asio::yield 执行顺序感到困惑

Posted

技术标签:

【中文标题】对同一链中的 boost::asio::yield 执行顺序感到困惑【英文标题】:Confused about boost::asio::yield order of execution in same strand 【发布时间】:2016-11-27 11:18:15 【问题描述】:

我正在尝试使用 asio 编写一个客户端,它执行以下操作:

    连接到服务器。 尝试在连接到服务器后读回一些数据。

我发现的问题是操作似乎不像我所期望的那样按顺序执行。这是代码:

std::future<NetMessage> Cliente::asyncConnectTo(std::string const & hostname,
                                          int port,
                                          std::string const & name) 
    using namespace std::literals;
    boost::asio::spawn
        (strand_,
         [this, name, port, hostname](boost::asio::yield_context yield) mutable 
            i_->playerLog->trace() << name << " connecting to " << hostname << ':'
                                   << port;
            Guinyote::Utils::connectWith
                (*this, std::move(hostname), port,
                 std::move(name), yield);

            i_->playerLog->info() << "Connected to server.";
        );
    runthread_ = std::thread([&] 
            try 
                i_->playerLog->info() << "Starting...";
                this->service_.run();
            
            catch (std::exception & e) 
                std::cout << e.what() << std::endl;
            
        );
    return this->asyncReceiveMessage(); //This function spawns another coroutine through the same strand_ object.

函数this-&gt;asyncReceiveMessage()预计会收到服务器连接后发回的消息:

std::future<NetMessage> Cliente::asyncReceiveMessage() 
    namespace ba = boost::asio;

    std::promise<NetMessage> prom;
    std::future<NetMessage> message = prom.get_future();
    ba::spawn
        (strand_,
         [this, p = std::move(prom)](boost::asio::yield_context yield) mutable 
            i_->playerLog->trace("waiting to read message from server socket...");
            boost::system::error_code ec;
            boost::int64_t messageSize;
            ba::async_read(
                socket_,
                ba::buffer(&messageSize, sizeof(boost::int64_t)),
                yield);

            i_->playerLog->trace() << "Client: Received message of "
                                   << messageSize << " bytes. Reading message...";

            std::vector<char> serverMessageData(messageSize);
            ba::async_read
                (socket_,
                 ba::buffer(serverMessageData),
                 yield);

            using namespace boost::iostreams;
            basic_array_source<char> input_source(&serverMessageData[0], serverMessageData.size());
            stream<basic_array_source<char>> stream(input_source);
            boost::archive::binary_iarchive archive(stream);
            Utils::MensajeRed msg;

            archive >> msg;
            i_->playerLog->trace() << "NetMessage correctly read.";
            p.set_value(std::move(msg));

        );
    return message;

在我的日志文件中,我在客户端得到以下信息:

[clientLog] [info] Client: Starting...
[clientLog] [trace] User1234 connecting to localhost:10004
[clientLog] [trace] waiting to read message from server socket...

但我希望该日志的第三行会出现在[clientLog] [info] Connected to server. 之后所以我的期望如下:

[clientLog] [info] Client: Starting...
[clientLog] [trace] User1234 connecting to localhost:10004
[clientLog] [info] Connected to server.
[clientLog] [trace] waiting to read message from server socket...

也就是说,“连接到服务器”应该总是发生在之前“等待从服务器套接字读取消息...”。

有人知道发生了什么吗?我以为strand_ 会保证执行顺序,但似乎我可能误解了一些东西。获得我想要的效果的正确解决方案是什么?

【问题讨论】:

我看到这是一样的,但我没有看到合适的解决方案。它实际上只是说类似“手动处理”:***.com/questions/19946555/… 【参考方案1】:

所以这里的问题是任务将按顺序启动,但后续boost::asio::spawn链中的spawn调用并不意味着第一个任务将在第二个之前完成。

这只是第一个任务在第二个任务之前开始,没有别的。

为了保持顺序,我只是创建了一个在 asyncConnectTo 的 spawn 内部调用的协程,而不是生成两个不同的协程。这样我可以确保第一个协程在第二个协程之前完成:

NetMessage Cliente::asyncReceiveMessageCoro(boost::asio::yield_context yield) 
    namespace ba = boost::asio;
    i_->playerLog->trace("waiting to read message from server socket...");
    boost::system::error_code ec;
    boost::int64_t messageSize;
    ba::async_read(
      socket_,
      ba::buffer(&messageSize, sizeof(boost::int64_t)),
      yield);

    i_->playerLog->trace() << "Client: Received message of "
                                   << messageSize << " bytes. Reading message...";

    std::vector<char> serverMessageData(messageSize);
    ba::async_read
       (socket_,
        ba::buffer(serverMessageData),
        yield);

    using namespace boost::iostreams;
    basic_array_source<char> input_source(&serverMessageData[0], serverMessageData.size());
    stream<basic_array_source<char>> stream(input_source);
    boost::archive::binary_iarchive archive(stream);
    Utils::MensajeRed msg;

    archive >> msg;
    i_->playerLog->trace() << "NetMessage correctly read.";
    return msg;

这个协程最后可以链接起来:

std::future<NetMessage> Cliente::asyncConnectTo(std::string const & hostname,
                                          int port,
                                          std::string const & name) 
    using namespace std::literals;

    std::promise<NetMessage> msgProm;
    auto msg = msgProm.get_future();
    boost::asio::spawn
        (strand_,
         [this, name, port, hostname, p = std::move(msgProm)](boost::asio::yield_context yield) mutable 
            i_->playerLog->trace() << name << " connecting to " << hostname << ':'
                                   << port;
            Guinyote::Utils::connectWith
                (*this, std::move(hostname), port,
                 std::move(name), yield);

            i_->playerLog->info() << "Connected to server.";
            p.set_value(this->asyncReceiveCoro(yield));
        );
    runthread_ = std::thread([&] 
            try 
                i_->playerLog->info() << "Starting...";
                this->service_.run();
            
            catch (std::exception & e) 
                std::cout << e.what() << std::endl;
            
        );
    return msg;

我的旧asyncReceiveMessage 只是spawn + 调用asyncReceiveMessageCoro 的组合。

【讨论】:

感谢您抽出宝贵时间分享您的发现

以上是关于对同一链中的 boost::asio::yield 执行顺序感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

如何轻松理解区块链中的非对称加密?

简述如何实现区块链中的JVM

[老k说区块链]区块链中的共识— 免信任的共识机制

路由器中同一网段或者不同网段的数据通信流程分析

信息摘要算法之七:SHA在区块链中的应用

区块链中的密码学之数字签名方案