boost asio stable_timer上的多个递归async_wait

Posted

技术标签:

【中文标题】boost asio stable_timer上的多个递归async_wait【英文标题】:Multiple recursive async_wait on boost asio steady_timer 【发布时间】:2019-01-17 17:25:42 【问题描述】:

这个问题的灵感来自 boost asio 文档 (link) 中关于异步计时器的教程。代码稍作修改,效果更明显。

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

代码非常简单,如果重复的行被注释掉,就可以正常工作,如下所示。

    持续时间为1ssteady_timer 调用async_wait

    当它过期时,将调用处理程序。在处理程序内部,定时器的生命周期再延长一秒,定时器再次调用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 20getting 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】:

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

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

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

这种情况一直持续下去,直到处理程序经常取消自己,以至于count==0 并且只有一个计时器正在运行。由于过期日期每次都转发了 1s,所以代码仍然等待整整 40s 过去。

【讨论】:

我认为你是对的。我在处理程序中添加了一个错误检查,它确实显示了operation_aborted 错误。但我还没有了解每一个细节。当我完全理解后,我会接受你的回答。 "在您的示例中,当第二个计时器完成时,它会调用 expires_at 以将计时器转发一秒。"为什么第二个计时器会完成并取消第一个正在运行的等待调用,而不是相反?例如,第一个计时器完成,调用expires_at,取消第二个等待调用。 你是对的,它是第一个经过的计时器启动序列。我更新了答案。 这很奇怪。我又做了一个测试。它实际上是第二个计时器取消第一个计时器的事件。我为我的测试创建了一个要点。 gist.github.com/mywtfmp3/ff64371a6e82930ee2f355a7a9e96965。然而,这听起来像是另一个问题。您是否希望我在这里接受答案并打开另一个问题? 这听起来像是另一个问题,因为原始问题的根本原因(为什么一次打印所有内容)已经确定。

以上是关于boost asio stable_timer上的多个递归async_wait的主要内容,如果未能解决你的问题,请参考以下文章

使用 Boost ASIO 多播到 macOS 上的特定接口

Windows上的Boost asio套接字无法异步连接

HTTP 请求上的 Boost.Asio SSL 错误,错误消息为“http request”

当服务器关闭时,客户端上的 boost asio 写操作被阻止

Boost.Asio - 轮询命名管道

为啥在使用 boost::asio 时每个连接都需要 strand?