ASIO documentation for basic_deadline_timer::cancel()
具有以下备注部分:
如果在调用
cancel()
时计时器已经到期,则用于异步等待操作的处理程序将:
- 已经被调用;或
- 已经在不久的将来排队等待调用。
这些处理程序无法再被取消,因此 传递了一个错误代码,指示成功完成了等待操作 。
重点已由我添加。通常,当您在计时器上调用cancel()
时,将以错误代码“用户取消操作”运行回调。但是,这实际上很少会用成功错误代码来调用它。我认为这是想说可能发生以下情况:
async_wait(myTimerHandler)
,其中myTimerHandler()
是用户回调函数。io_context::post(cancelMyTimer)
,其中cancelMyTimer()
是用户回调函数。现在已排队等待在线程A中调用。cancelMyTimer()
,该线程在计时器上调用cancel()
。但是计时器已经触发,并且ASIO不会检查处理程序是否仍在排队并且没有执行,因此此操作无济于事。myTimerHandler
,并且不会检查在此期间是否cancel()
被调用,因此它仍然成功作为错误代码传递。请记住,此示例仅具有一个调用io_context::run()
,deadline_timer::async_wait
或deadline_timer::cancel()
的线程。在另一个线程中发生的唯一事情是对post()
的调用,该调用是为了避免出现任何竞争条件而发生的。这一系列事件可能吗?还是它是指某种多线程方案(考虑到计时器不是线程安全的,这似乎不太可能)?
上下文:如果您有一个希望定期重复的计时器,那么显而易见的事情是检查回调中的错误代码,如果代码成功,则再次设置计时器。如果可以进行上述比赛,则必须有一个单独的变量来说明是否取消了计时器,除了调用cancel()
以外,还需要更新该计时器。
答案 0 :(得分:1)
您所说的一切都是正确的。因此,根据您的情况,您可能需要一个单独的变量来表示您不想继续循环。我通常使用atomic_bool,并且不必费心发布取消例程,只需在我所处的任何线程中设置bool&call cancel。
更新:
我的答案的来源主要是多年使用ASIO的经验以及对asio代码库的了解足以解决问题并在需要时扩展它的一部分。
是的,该文档说在截止截止时间的共享实例之间不是线程安全的,但是该文档不是最好的(什么文档是...)。如果您查看“取消”工作原理的来源,我们可以看到:
提升Asio版本1.69:boost \ asio \ detail \ impl \ win_iocp_io_context.hpp
template <typename Time_Traits>
std::size_t win_iocp_io_context::cancel_timer(timer_queue<Time_Traits>& queue,
typename timer_queue<Time_Traits>::per_timer_data& timer,
std::size_t max_cancelled)
{
// If the service has been shut down we silently ignore the cancellation.
if (::InterlockedExchangeAdd(&shutdown_, 0) != 0)
return 0;
mutex::scoped_lock lock(dispatch_mutex_);
op_queue<win_iocp_operation> ops;
std::size_t n = queue.cancel_timer(timer, ops, max_cancelled);
post_deferred_completions(ops);
return n;
}
您可以看到取消操作由互斥锁保护,因此“取消”操作是线程安全的。
不是在截止时间计时器上调用大多数其他操作(关于从多个线程同时调用它们)。
我也认为您对以快速顺序重新启动计时器是正确的。我通常没有用这种方式停止和启动计时器的用例,所以我从来不需要这样做。
答案 1 :(得分:1)
您甚至不需要第二个线程就可以进入basic_waitable_timer::cancel()
调用太晚的情况(因为计时器的(完成)处理程序已经排队)。
您的程序要与尚未恢复的basic_waitable_timer::async_wait()
同时执行一些其他异步操作就足够了。如果您然后仅依靠basic_waitable_timer::cancel()
进行取消,则来自另一个异步(完成)处理程序的cancel()
调用将与已调度的async_wait()
处理程序竞争:
如果在调用cancel()时计时器已经过期,则用于异步等待操作的处理程序将:
- 已经被调用;或
- 已在不久的将来排队等待调用。
这些处理程序无法再被取消,因此会传递一个错误代码,指示成功完成等待操作。
(basic_waitable_timer::cancel(),重点是我,即比赛情况是由于第二种情况引起的)
一个单线程的现实示例(即程序没有显式启动任何线程,仅调用io_server.run()
一次)并包含描述的种族:
void Fetch_Timer::resume()
{
timer_.expires_from_now(std::chrono::seconds(1));
timer_.async_wait([this](const boost::system::error_code &ec)
{
BOOST_LOG_FUNCTION();
if (ec) {
if (ec.value() == boost::asio::error::operation_aborted)
return;
THROW_ERROR(ec);
} else {
print();
resume();
}
});
}
void Fetch_Timer::stop()
{
print();
timer_.cancel();
}
(来源:imapdl/copy/fetch_timer.cc)
在此示例中,obvious fix(即也查询布尔标志)甚至不需要使用任何同步原语(例如原子),因为该程序是单线程的。这意味着它可以并发执行(异步)操作,但不能并行执行。
(在上面的示例中,FWIW,该漏洞仅每2年左右就出现一次,即使每天使用也是如此)