Boost Asio上的多个递归async_wait

时间:2019-01-17 17:25:42

标签: c++ boost boost-asio

此问题的灵感来自boost asio文档(link)中的异步计时器教程。对代码进行了稍微的修改,以使效果更加明显。

有一个相关的问题,Multiple async_wait from a boost Asio deadline_timer。但我不确定该问题的答案是否适用于我的情况。

代码非常简单,并且如果注释掉重复的行,则可以按预期工作,如下所示。

  1. 持续时间steady_timer的{​​{1}}呼叫1s

  2. 到期时,将调用处理程序。在处理程序内部,计时器的寿命再延长一秒,计时器再次调用async_wait

  3. 变量async_wait(为20)用于限制计时器可以触发的次数。


count

此代码的输出如下所示。每隔一秒钟就会打印一条新行。

#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的所有行,在40秒内什么都不显示,然后打印最后一行。

getting 1

我的问题是,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构造函数中看到的那样。

1 个答案:

答案 0 :(得分:2)

该行为的答案似乎在于basic_waitable_timer::expires_at(const time_point & expiry_time)的文档中:

  

此功能设置到期时间。任何待处理的异步等待操作都将被取消。每个取消的操作的处理程序都将使用boost :: asio :: error :: operation_aborted错误代码调用。

在您的示例中,当第一个计时器结束时,它调用expires_at以便第二秒转发计时器。但是,这取消了第二个正在运行的await调用,现在将在下一个eventloop迭代中直接调用该调用,且发生operation_aborted错误。但是,由于您没有检查错误代码ec,所以看不到该错误代码。现在,此处理程序将再次直接转发计时器,从而取消最后一个已启动的async_wait

这种情况一直持续下去,直到处理程序经常取消自身以至count==0且仅运行一个计时器为止。由于到期时间每次都转发1秒,因此代码仍然等待40秒过去。