在std :: thread中增强asio阻塞操作而不是使用异步方法?

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

所以我一直在努力在Boost.Asio上创建一些抽象层。我想要原子地处理某些批次的操作,例如tcp::resolver::resolve()asio::connect()。如果我使用这两者的异步版本,代码变得非常讨厌,因为我必须“链接”回调。实质上:

  1. 用户调用我的Connect()包装器方法,该方法接受主机和服务字符串,并且它们还提供在连接完成时调用的回调。
  2. 使用主机和服务字符串参数调用resolver::async_resolve()。将用户的回调绑定到回调以进行解析(传递连接后要调用的回调)
  3. 从解决回调,如果成功,请调用asio::async_connect()。再次,将用户的回调绑定到connect回调。
  4. 在connect回调中,如果成功,则调用用户的回调

要么这是令人讨厌的,因为大量的嵌套lambda,或者讨厌,因为函数是分开的,现在我有一个加载样板的类。对我来说,做这样的事情要简单得多(不编译,所以现在只需将其视为伪代码):

   using ConnectCallback = std::function<void(std::shared_ptr<tcp::socket>)>;

   void Connect(std::string const& host, std::string const& service, ConnectCallback cb)
   {
      std::thread{[this, host, service, cb{std::move(cb)}]
      {
         std::shared_ptr<tcp::socket> socket;

         try
         {
            tcp::resolver r{m_context};
            auto endpoints = r.resolve(host, service);

            socket = std::make_shared<tcp::socket>(m_context);
            asio::connect(*socket, endpoints);
         }
         catch (std::exception const&)
         {
            // either resolve or connect failed / timed out
         }

         cb(std::move(socket));
      }}.detach();
   }

对我来说这更简单,至少对于启动连接,因为我不必担心这么多的回调。唯一的缺点是我不知道如何使用这种方法处理超时场景。与我在Google上找到的超时相关的所有解决方案都需要使用async_方法。

是建议以这种方式做事,还是我必须坚持使用异步方法?如果是后者,我可以使用哪些技术来简化回调链式样板?

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

如果编写处理程序对你很烦,你可以考虑使用coroutines。它适用于异步操作,使您能够实现超时。

struct Client2 {
    Client2(asio::io_context& io)
    : io(io) {}

    asio::io_context& io;
    asio::ip::tcp::resolver resolver{io};
    asio::ip::tcp::socket sock{io};
    asio::high_resolution_timer timer{io};
    atomic_bool stopped{false};

    void connect (const string& host, const string& service, int timeoutMs)
    {
        boost::asio::spawn(io,std::bind(&Client2::establishConnection,this,host,service,timeoutMs,std::placeholders::_1));
        boost::asio::spawn(io,std::bind(&Client2::doTimeout,this,std::placeholders::_1));
    }
    void establishConnection (string host, string service, int timeoutMs,boost::asio::yield_context yield)
    {
        try {
            timer.expires_after(std::chrono::milliseconds(timeoutMs)); // set timeout
            auto res = resolver.async_resolve(host,service,yield); 
            // resume here when handler for resolving was called
            if (stopped)
                return;
            asio::async_connect(sock,res,yield);
            timer.cancel(); // connection is established, do sth with sock here, cancel timer
        }
        catch (std::exception& ex) {
        }
    }
    void doTimeout (boost::asio::yield_context yield)
    {
        try {
            timer.async_wait(yield); // throw exception when was canceled by startConnecting
        }
        catch (std::exception& ex) {
            return;
        }
        resolver.cancel(); // timeout == timer expired, so cancel resolving and close socket
        sock.close();
        stopped = true;
    }
};

// in main
        asio::io_context io;
        Client2 client{io};
        client.connect("localhost","5444",200);
        thread th([&](){ io.run(); }); // call run from at least 2 threads
        io.run();  // establishConnection and doTimeout can be executed concurrently
        th.join();

我在代码中添加了一些注释。简而言之:使用两个协同程序。在establishConnection中执行了两个异步操作:async_resolveasync_connect。在doTimeout coroutine timer开始了。当计时器在建立连接之前到期时,我们取消解析并关闭套接字。如果在计时器到期之前建立了连接,我们取消计时器,我们可以使用sock执行一些操作。

establishConnectiondoTimeout的身体可以移动到lambda,作为asio::spawn函数的参数。因此,我们只能有一个成员函数,并且没有执行3个异步操作的代码的处理程序。如果这满足您,请开始使用协同程序。

© www.soinside.com 2019 - 2024. All rights reserved.