取消deadline_timer,无论如何都会触发回调

时间:2015-07-02 19:08:20

标签: c++ c++11 boost callback boost-asio

我很惊讶没有在boost :: asio(我们任何广泛使用的库)中找到时钟组件,所以它尝试制作一个简单的,简约的实现来测试我的一些代码。

使用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

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()看起来并不总是有效。我有些错误吗?

2 个答案:

答案 0 :(得分:2)

首先,让我们看一下转载的问题:

Live On Coliru (以下代码)

  

如您所见,我将其作为

运行
./a.out | grep -C5 false
     

确实 c1_active为假(并且预计不会运行完成处理程序)时,会过滤从C1的完成处理程序打印的记录的输出

简而言之,这个问题是一种“合乎逻辑”的竞争条件。

这有点令人费解,因为只有一个线程(表面上可见)。但它实际上并不太复杂。

这是怎么回事:

  • 当Clock C1到期时,它会将完成处理程序发布到io_service的任务队列中。这意味着它可能不会立即运行。

  • 想象C2也过期了,它的完成处理程序现在得到调度并在C1推送之前执行。想象一下,这次巧合,C2决定在C1上调用stop()

  • C2完成处理程序返回后,调用C1的完成处理程序。

    <强> OOPS

    它仍然有ec说“没有错误”......因此C1的截止时间计时器被重新安排。糟糕。

背景

有关Asio(不)对完成处理程序执行顺序的保证的更深入背景,请参阅

解决方案?

最简单的解决方案是实现 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);
    }
}

在我的系统上,它不再重现问题:)

复制者

<强> Live On Coliru

#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();
}

答案 1 :(得分:2)

来自增强文档:

  

如果在调用cancel()时定时器已经过期,那么异步等待操作的处理程序将:

     
      
  1. 已被调用;
  2.   
  3. 或在不久的将来排队等待调用。
  4.         

    这些处理程序无法再取消,因此也是如此   传递一个错误代码,表示成功完成   等待操作。

你的应用程序在这样的成功完成时(当计时器已经过期时)再次重新启动计时器,而另一个有趣的事情就是在Start函数再次调用你时会隐式取消计时器,以防万一它没有过期。

  

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

可能你可以重用你的m_enabled变量,或者只是有另一个标志来检测定时器取消。

另一种解决方案是可能的:timer example