Jonathan Boccara over at Fluent{C++} made a post a while ago titled A Simple Timer in C++. I felt things could be done… differently 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 cancellation event per set_timeout
or set_interval
call?
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
#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;
}
Program output:
waiting 5s…
timeout
interval
interval
interval
interval
waiting 5s…
interval
interval
interval
interval
interval
Program ended with exit code: 1
timer.h
#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
#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;
};