Несколько рекурсивных async_wait на повышение - PullRequest
0 голосов
/ 17 января 2019

Этот вопрос вдохновлен учебным пособием по асинхронному таймеру из документации boost asio ( link ). Код немного изменен, чтобы сделать эффект более очевидным.

Есть связанный вопрос, Несколько async_wait от наддува Asio deadline_timer . Но я не уверен, относится ли ответ на этот вопрос к моему делу.

Код довольно прост и работает, как и ожидалось, если дублирующая строка закомментирована, как показано ниже.

  1. A steady_timer с продолжительностью 1s звонков async_wait.

  2. Когда истекает срок действия, вызывается обработчик. Внутри обработчика время жизни таймера увеличивается еще на одну секунду, и таймер снова вызывает async_wait.

  3. Переменная count из 20 используется для ограничения количества срабатываний таймера.


#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>

namespace asio = boost::asio;

void bind_handler(const boost::system::error_code& ec,
                  asio::steady_timer& t,
                  int count) {
  if (count > 0) {
    std::cout << "getting " << count << "\n";
    t.expires_at(t.expiry() + std::chrono::seconds(1));
    t.async_wait(boost::bind(bind_handler, asio::placeholders::error,
                             boost::ref(t), --count));
  }
}

int main() {
  asio::io_context io_context(1);
  asio::steady_timer t(io_context, std::chrono::seconds(1));

  int count = 20;

  t.async_wait(boost::bind(bind_handler, asio::placeholders::error,
                           boost::ref(t), count));
  //t.async_wait(boost::bind(bind_handler, asio::placeholders::error,
  //                         boost::ref(t), count));

  auto start = std::chrono::steady_clock::now();
  io_context.run();
  auto end = std::chrono::steady_clock::now();
  std::cout
      << std::chrono::duration_cast<std::chrono::seconds>(end - start).count()
      << " seconds passed\n";

  return 0;
}

Вывод этого кода показан ниже. Новая строка печатается с каждой проходящей секундой.

getting 20
getting 19
getting 18
...lines...
...omitted...
getting 3
getting 2
getting 1
21 seconds passed

Однако, если две строки в приведенном выше коде не закомментированы, программа ведет себя очень по-разному. Вывод наклеен ниже. Программа печатает все строки от getting 20 до getting 1 в течение секунды, ничего не показывает в течение 40 секунд, а затем печатает последнюю строку.

getting 20
getting 20
getting 19
getting 19
getting 18
getting 18
...lines...
...omitted...
getting 3
getting 3
getting 2
getting 2
getting 1
getting 1
41 seconds passed

Мой вопрос: как многократный рекурсивный вызов async_wait влияет на поведение программы? Я чувствую, что происходит какая-то гонка данных, но цифры все еще печатаются последовательно. Кроме того, задействован только один поток, как мы видим в конструкторе io_context.

1 Ответ

0 голосов
/ 18 января 2019

Кажется, что ответ для поведения лежит в документации для basic_waitable_timer::expires_at(const time_point & expiry_time):

Эта функция устанавливает время истечения. Все ожидающие асинхронные операции ожидания будут отменены. Обработчик для каждой отмененной операции будет вызываться с кодом ошибки boost :: asio :: error :: operation_aborted.

В вашем примере, когда заканчивается первый таймер, он вызывает expires_at, чтобы переслать таймер в секунду. Однако это отменяет второй запущенный вызов await, который теперь будет вызываться непосредственно в следующей итерации цикла с ошибкой operation_aborted. Однако, поскольку вы не проверяете код ошибки ec, вы этого не видите. Теперь этот обработчик будет снова напрямую пересылать таймер и тем самым отменять последний async_wait, который был запущен.

Это продолжается до тех пор, пока обработчики не отменят себя достаточно часто, чтобы count==0 работал только один таймер. Поскольку дата истечения срока действия пересылается каждый раз по 1 с, код все еще ожидает истечения целых 40 с.

...