Jonathan Boccara over at Fluent{C++} made a post a while ago titled A Simple Timer in C++. I felt things could be done… different 😉 so I decided to write my own version of the timer code.
First, I felt there’s no need to actually instantiate
timer objects; a simple function call to
set_timeout or
set_interval from
namespace timer should be sufficient.
Second, I didn’t like the way cancellation was done. Single
stop call interrupted all intervals and timeouts. How about a cancelation event per
set_timeout or
set_intervalcall?
Finally, I wanted the
set_timeout and
set_interval functions to accept any callable with any number of arguments.
That’s exactly how I designed my interface.
Usage example:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#include <iostream> #include <mutex> #include "timer.h" using namespace std; mutex cout_lock; #define trace(x) { scoped_lock<mutex> lock(cout_lock); cout << x << endl; } int main(int argc, char** argv) { auto e1 = timer::set_timeout(1s, []() { trace("timeout"); }); auto e2 = timer::set_timeout(6s, []() { trace("canceled timeout"); }); auto e3 = timer::set_interval(1s, []() { trace("interval"); }); auto e4 = timer::set_interval(6s, []() { trace("canceled interval"); }); trace("waiting 5s..."); this_thread::sleep_for(5s); e2->signal(); e4->signal(); trace("waiting 5s..."); this_thread::sleep_for(5s); return 1; } |
waiting 5s…
Program output.
timeout
interval
interval
interval
interval
waiting 5s…
interval
interval
interval
interval
interval
Program ended with exit code: 1
timer.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
#pragma once #include <thread> #include <memory> #include "event.h" namespace timer { template<typename D, typename F, typename... Args> std::shared_ptr<manual_event> set_timeout(D d, F f, Args&&... args) { auto event = std::make_shared<manual_event>(); std::thread([=]() { if(event->wait_for(d)) return; f(args...); }).detach(); return event; } template<typename D, typename F, typename... Args> std::shared_ptr<manual_event> set_interval(D d, F f, Args&&... args) { auto event = std::make_shared<manual_event>(); std::thread([=]() { while(true) { if(event->wait_for(d)) return; f(args...); } }).detach(); return event; } } |
Updated event.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
#pragma once #include <mutex> #include <condition_variable> class manual_event { public: explicit manual_event(bool signaled = false) noexcept : m_signaled(signaled) {} void signal() noexcept { { std::unique_lock<std::mutex> lock(m_mutex); m_signaled = true; } m_cv.notify_all(); } void wait() noexcept { std::unique_lock<std::mutex> lock(m_mutex); m_cv.wait(lock, [&](){ return m_signaled != false; }); } template<typename T> bool wait_for(T t) noexcept { std::unique_lock<std::mutex> lock(m_mutex); return m_cv.wait_for(lock, t, [&](){ return m_signaled != false; }); } template<typename T> bool wait_until(T t) noexcept { std::unique_lock<std::mutex> lock(m_mutex); return m_cv.wait_until(lock, t, [&](){ return m_signaled != false; }); } void reset() noexcept { std::unique_lock<std::mutex> lock(m_mutex); m_signaled = false; } private: bool m_signaled = false; std::mutex m_mutex; std::condition_variable m_cv; }; |