取消一个 deadline_timer,无论如何都会触发回调
cancel a deadline_timer, callback triggered anyway
我很惊讶没有在 boost::asio(我们任何广泛使用的库)中找到时钟组件,所以它尝试制作一个简单、简约的实现来测试我的一些代码。
使用 boost::asio::deadline_timer
我做了以下 class
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
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 不会停止一秒钟,而它应该。
由于某些同步问题,调用 m_timer.cancel()
似乎并不总是有效。我是不是搞错了什么?
首先,让我们展示一下重现的问题:
Live On Coliru(代码如下)
As you can see I run it as
./a.out | grep -C5 false
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)
简而言之,问题是 "logical" 竞争条件。
这有点令人费解,因为只有一个线程(表面上可见)。但其实并没有太复杂。
这是怎么回事:
当时钟 C1 到期时,它将post其完成处理程序放入io_service
的任务队列。这意味着它可能不会立即 运行。
想象一下 C2 也过期了,它的完成处理程序现在被安排并在 C1 刚刚推送的处理程序之前执行。想象一下,由于这次巧合,C2 决定在 C1 上调用 stop()
。
在 C2 的完成处理程序 returns 之后,C1 的完成处理程序被调用。
糟糕
它仍然有 ec
说 "no error"... 因此 C1 的截止时间计时器被重新安排。哎呀
背景
有关 Asio(不)为完成处理程序的执行顺序做出的保证的更深入背景,请参阅
- When do handlers for cancelled boost::asio handlers get to run?
解决方案?
最简单的解决方案是意识到 m_enabled
可以是false
。让我们添加支票:
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);
}
}
在我的系统上它不再重现问题:)
复制者
#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();
}
来自 boost 文档:
If the timer has already expired when cancel() is called, then the handlers for asynchronous wait operations will:
- have already been invoked;
- or have been queued for invocation in the near future.
These handlers can no longer be cancelled, and therefore
are passed an error code that indicates the successful completion of
the wait operation.
您的应用程序在此类成功完成时(当计时器已经过期时)再次重新启动计时器,另一个有趣的事情是在 Start 函数上调用您再次隐式取消计时器以防它尚未过期。
expires_at function sets the expiry time. Any pending asynchronous
wait operations will be cancelled. The handler for each cancelled
operation will be invoked with the
boost::asio::error::operation_aborted error code.
也许您可以重复使用 m_enabled 变量,或者只使用另一个标志来检测计时器取消。
另一种解决方案是可能的:timer example
我很惊讶没有在 boost::asio(我们任何广泛使用的库)中找到时钟组件,所以它尝试制作一个简单、简约的实现来测试我的一些代码。
使用 boost::asio::deadline_timer
我做了以下 class
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
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 不会停止一秒钟,而它应该。
由于某些同步问题,调用 m_timer.cancel()
似乎并不总是有效。我是不是搞错了什么?
首先,让我们展示一下重现的问题:
Live On Coliru(代码如下)
As you can see I run it as
./a.out | grep -C5 false
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)
简而言之,问题是 "logical" 竞争条件。
这有点令人费解,因为只有一个线程(表面上可见)。但其实并没有太复杂。
这是怎么回事:
当时钟 C1 到期时,它将post其完成处理程序放入
io_service
的任务队列。这意味着它可能不会立即 运行。想象一下 C2 也过期了,它的完成处理程序现在被安排并在 C1 刚刚推送的处理程序之前执行。想象一下,由于这次巧合,C2 决定在 C1 上调用
stop()
。在 C2 的完成处理程序 returns 之后,C1 的完成处理程序被调用。
糟糕
它仍然有
ec
说 "no error"... 因此 C1 的截止时间计时器被重新安排。哎呀
背景
有关 Asio(不)为完成处理程序的执行顺序做出的保证的更深入背景,请参阅
- When do handlers for cancelled boost::asio handlers get to run?
解决方案?
最简单的解决方案是意识到 m_enabled
可以是false
。让我们添加支票:
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);
}
}
在我的系统上它不再重现问题:)
复制者
#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();
}
来自 boost 文档:
If the timer has already expired when cancel() is called, then the handlers for asynchronous wait operations will:
- have already been invoked;
- or have been queued for invocation in the near future.
These handlers can no longer be cancelled, and therefore are passed an error code that indicates the successful completion of the wait operation.
您的应用程序在此类成功完成时(当计时器已经过期时)再次重新启动计时器,另一个有趣的事情是在 Start 函数上调用您再次隐式取消计时器以防它尚未过期。
expires_at function sets the expiry time. Any pending asynchronous wait operations will be cancelled. The handler for each cancelled operation will be invoked with the boost::asio::error::operation_aborted error code.
也许您可以重复使用 m_enabled 变量,或者只使用另一个标志来检测计时器取消。
另一种解决方案是可能的:timer example