取消心动的deadline_timer [英] cancel a deadline_timer, callback triggered anyway
问题描述
我很惊讶没有在boost :: asio(我们广泛使用的库)中找到时钟组件,因此它尝试创建一种简单,简约的实现来测试我的一些代码.
I was suprised not to find a clock component in boost::asio (our any widely used library) so it tried making a simple, minimalistic, implementation for testing some of my code.
使用boost::asio::deadline_timer
我做了以下课程
class Clock
{
public:
using callback_t = std::function<void(int, Clock&)>;
using duration_t = boost::posix_time::time_duration;
public:
Clock(boost::asio::io_service& io,
callback_t callback = nullptr,
duration_t duration = boost::posix_time::seconds(1),
bool enable = true)
: m_timer(io)
, m_duration(duration)
, m_callback(callback)
, m_enabled(false)
, m_count(0ul)
{
if (enable) start();
}
void start()
{
if (!m_enabled)
{
m_enabled = true;
m_timer.expires_from_now(m_duration);
m_timer.async_wait(boost::bind(&Clock::tick, this, _1)); // std::bind _1 issue ?
}
}
void stop()
{
if (m_enabled)
{
m_enabled = false;
size_t c_cnt = m_timer.cancel();
#ifdef DEBUG
printf("[DEBUG@%p] timer::stop : %lu ops cancelled\n", this, c_cnt);
#endif
}
}
void tick(const boost::system::error_code& ec)
{
if(!ec)
{
m_timer.expires_at(m_timer.expires_at() + m_duration);
m_timer.async_wait(boost::bind(&Clock::tick, this, _1)); // std::bind _1 issue ?
if (m_callback) m_callback(++m_count, *this);
}
}
void reset_count() { m_count = 0ul; }
size_t get_count() const { return m_count; }
void set_duration(duration_t duration) { m_duration = duration; }
const duration_t& get_duration() const { return m_duration; }
void set_callback(callback_t callback) { m_callback = callback; }
const callback_t& get_callback() const { return m_callback; }
private:
boost::asio::deadline_timer m_timer;
duration_t m_duration;
callback_t m_callback;
bool m_enabled;
size_t m_count;
};
但是,看起来stop
方法不起作用.如果我要求Clock c2
停止另一个Clock c1
Yet it looks like the stop
method doesn't work. If I ask a Clock c2
to stop another Clock c1
boost::asio::io_service ios;
Clock c1(ios, [&](int i, Clock& self){
printf("[C1 - fast] tick %d\n", i);
}, boost::posix_time::millisec(100)
);
Clock c2(ios, [&](int i, Clock& self){
printf("[C2 - slow] tick %d\n", i);
if (i%2==0) c1.start(); else c1.stop(); // Stop and start
}, boost::posix_time::millisec(1000)
);
ios.run();
我看到两个时钟都按预期的那样跳动,有时c1不会停止一秒钟,而应该停止.
I see both clocks ticking as expected expect sometimes c1 doesn't stop for one second, while it should.
由于某些同步问题,调用m_timer.cancel()
似乎并不总是有效.我错了吗?
It looks like calling m_timer.cancel()
doesn't always work because of some sync issue. Did I got somethign wrong ?
推荐答案
首先,让我们展示一下重现的问题:
First, let's show the problem reproduced:
在Coliru上直播 (下面的代码)
如您所见,我将其运行为
As you can see I run it as
./a.out | grep -C5 false
此过滤器过滤确实 c1_active
为false(并且预期不会运行完成处理程序)时从C1的完成处理程序打印的记录的输出
This filters the output for records that print from C1's completion handler when really c1_active
is false (and the completion handler wasn't expected to run)
总的来说,问题是逻辑"竞争条件.
The problem, in a nutshell, is a "logical" race condition.
有点麻烦,因为只有一条线(在表面可见).但这实际上并不太复杂.
It's a bit of mind bender because there's only a single thread (visible on the surface). But it's actually not too complicated.
这是怎么回事:
-
当时钟C1到期时,它将把其完成处理程序发布到
io_service
的任务队列中.这意味着它可能不会立即运行.
when Clock C1 expires, it will post its completion handler onto the
io_service
's task queue. Which implies that it might not run immediately.
想象C2也过期了,现在它的完成处理程序已被调度并在C1刚刚推送的处理程序之前执行.想象一下,这次巧合,C2决定在C1上调用stop()
.
imagine that C2 expired too, and it's completion handler now gets scheduled and executes before the one that C1 just pushed. Imagine that by some high coincidence this time, C2 decides to call stop()
on C1.
返回C2的完成处理程序后,将调用C1的完成处理程序.
After C2's completion handler returns, C1's completion handler gets invoked.
OOPS
OOPS
它仍然有ec
说没有错误" ...因此,C1的截止日期计时器被重新安排了.糟糕!
It still has ec
saying "no error"... Hence the deadline timer for C1 gets rescheduled. Oops.
有关Asio(不)为完成处理程序执行顺序所做的保证的更深入了解,请参见
For a more in-depth background on the guarantees that Asio (doesn't) make(s) for the order in which completion handlers get executed, see
最简单的解决方案是认识 m_enabled
可能是false
.我们只添加支票:
The simplest solution is to realize that m_enabled
could be false
. Let's just add the check:
void tick(const boost::system::error_code &ec) {
if (!ec && m_enabled) {
m_timer.expires_at(m_timer.expires_at() + m_duration);
m_timer.async_wait(boost::bind(&Clock::tick, this, _1));
if (m_callback)
m_callback(++m_count, *this);
}
}
在我的系统上,它不再重现此问题:)
On my system it doesn't reproduce the problem any more :)
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time_io.hpp>
static boost::posix_time::time_duration elapsed() {
using namespace boost::posix_time;
static ptime const t0 = microsec_clock::local_time();
return (microsec_clock::local_time() - t0);
}
class Clock {
public:
using callback_t = std::function<void(int, Clock &)>;
using duration_t = boost::posix_time::time_duration;
public:
Clock(boost::asio::io_service &io, callback_t callback = nullptr,
duration_t duration = boost::posix_time::seconds(1), bool enable = true)
: m_timer(io), m_duration(duration), m_callback(callback), m_enabled(false), m_count(0ul)
{
if (enable)
start();
}
void start() {
if (!m_enabled) {
m_enabled = true;
m_timer.expires_from_now(m_duration);
m_timer.async_wait(boost::bind(&Clock::tick, this, _1)); // std::bind _1 issue ?
}
}
void stop() {
if (m_enabled) {
m_enabled = false;
size_t c_cnt = m_timer.cancel();
#ifdef DEBUG
printf("[DEBUG@%p] timer::stop : %lu ops cancelled\n", this, c_cnt);
#endif
}
}
void tick(const boost::system::error_code &ec) {
if (ec != boost::asio::error::operation_aborted) {
m_timer.expires_at(m_timer.expires_at() + m_duration);
m_timer.async_wait(boost::bind(&Clock::tick, this, _1));
if (m_callback)
m_callback(++m_count, *this);
}
}
void reset_count() { m_count = 0ul; }
size_t get_count() const { return m_count; }
void set_duration(duration_t duration) { m_duration = duration; }
const duration_t &get_duration() const { return m_duration; }
void set_callback(callback_t callback) { m_callback = callback; }
const callback_t &get_callback() const { return m_callback; }
private:
boost::asio::deadline_timer m_timer;
duration_t m_duration;
callback_t m_callback;
bool m_enabled;
size_t m_count;
};
#include <iostream>
int main() {
boost::asio::io_service ios;
bool c1_active = true;
Clock c1(ios, [&](int i, Clock& self)
{
std::cout << elapsed() << "\t[C1 - fast] tick" << i << " (c1 active? " << std::boolalpha << c1_active << ")\n";
},
boost::posix_time::millisec(1)
);
#if 1
Clock c2(ios, [&](int i, Clock& self)
{
std::cout << elapsed() << "\t[C2 - slow] tick" << i << "\n";
c1_active = (i % 2 == 0);
if (c1_active)
c1.start();
else
c1.stop();
},
boost::posix_time::millisec(10)
);
#endif
ios.run();
}
这篇关于取消心动的deadline_timer的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!