Усилить операции блокировки asio в std :: thread вместо использования асинхронных методов? - PullRequest
0 голосов
/ 02 марта 2019

Так что я боролся с созданием некоторого слоя абстракции над Boost.Asio.Есть определенные пакеты операций, которые я хочу обработать атомарно, например tcp::resolver::resolve() и asio::connect().Если я использую асинхронную версию обоих, код становится очень неприятным, потому что мне приходится «цеплять» обратные вызовы.По сути:

  1. Пользователь вызывает мой Connect() метод-обертку, который принимает хост и служебную строку, а также обеспечивает обратный вызов, который вызывается, когда соединение установлено.
  2. Вызвать resolver::async_resolve(), используя параметры строки host и service.Привязать обратный вызов пользователя к обратному вызову для разрешения (для передачи обратного вызова, который будет вызван после подключения)
  3. Из обратного вызова разрешения, в случае успеха вызовите asio::async_connect().Снова, свяжите обратный вызов пользователя с обратным вызовом подключения.
  4. В обратном вызове подключения, в случае успеха, вызовите обратный вызов пользователя

Либо это неприятно из-за тонны вложенных лямбд,или неприятно, потому что функции разделены, и теперь у меня есть класс, загруженный с шаблоном.Мне кажется, было бы намного проще просто сделать что-то подобное (не компилировать, так что пока просто воспринимайте это как псевдокод):

   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_.

Рекомендуется ли действовать таким образом или я должен придерживаться асинхронных методов?И если последнее, какие методы я могу использовать, чтобы упростить шаблон цепочки обратных вызовов?

1 Ответ

0 голосов
/ 03 марта 2019

Если написание обработчиков раздражает вас, вы можете рассмотреть возможность использования сопрограмм .Он работает с асинхронными операциями и позволяет реализовать время ожидания.

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_resolve и async_connectdoTimeout запускается сопрограмма timer.Когда таймер истекает до установления соединения, мы отменяем разрешение и закрываем сокет.Если соединение было установлено до истечения таймера, мы отменяем таймер и можем выполнить некоторые операции с sock.

Тела establishConnection и doTimeout могут быть перемещены в лямбду в качестве аргументаasio::spawn функция.Таким образом, мы могли бы иметь только одну функцию-член и не иметь обработчиков для кода, где выполняются 3 асинхронные операции.Если это вас устраивает, начните использовать сопрограммы.

...