I needed a task scheduler that could be used somehow like this:
#include <iostream>
// To compile and run this example, include here the code listed in the second code block
void Task2()
{
std::cout << "OK2 ! now is " << std::chrono::system_clock::now().time_since_epoch().count() << std::endl;
}
void Task3()
{
std::cout << "--3 " << std::endl;
}
void Task1(Scheduler & sch)
{
auto now = std::chrono::system_clock::now();
std::cout << "OK1 ! now is " << now.time_since_epoch().count() << std::endl;
sch.ScheduleAt(now + std::chrono::seconds(1), []{ Task2(); });
sch.ScheduleAt(now + std::chrono::seconds(2), []{ Task2(); });
sch.ScheduleAt(now + std::chrono::seconds(3), []{ Task2(); });
}
void main()
{
auto now = std::chrono::system_clock::now();
Scheduler sch;
sch.ScheduleAt(now + std::chrono::seconds(15), [&sch]{ Task1(sch); });
sch.ScheduleAt(now + std::chrono::seconds(20), [&sch]{ Task1(sch); });
sch.ScheduleAt(now + std::chrono::seconds(25), [&sch]{ Task1(sch); });
sch.ScheduleAt(now + std::chrono::seconds( 2), [&sch]{ Task2(); });
sch.ScheduleEvery(std::chrono::seconds(1), []{ Task3(); });
getchar();
}
It had to have tasks scheduled once and repetitive tasks, and should stop and clean itself up gracefully on destruction even while running. I did not care for parallelism: tasks that should run in their own threads should manage it. It had to accept lambdas for simplicity.
I could not find anything similar in Boost or POCO, though I did not search very hard as I was interested in writing it myself. There are some related questions in C#, but I need C++.
#include <map>
#include <functional>
#include <chrono>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <memory>
#include <boost/noncopyable.hpp>
class Scheduler : boost::noncopyable
{
private:
std::multimap<std::chrono::system_clock::time_point, std::function<void()>> tasks;
std::mutex mutex;
std::unique_ptr<std::thread> thread;
std::condition_variable blocker;
bool go_on;
public:
Scheduler()
:go_on(true)
{
thread.reset(new std::thread([this](){ this->ThreadLoop(); }));
}
~Scheduler()
{
go_on = false;
ScheduleAt(std::chrono::system_clock::now(), [](){});
thread->join();
}
void ThreadLoop()
{
while(go_on)
{
std::function<void()> todo;
{
std::unique_lock<std::mutex> lock(mutex);
auto now = std::chrono::system_clock::now();
if ( tasks.empty()==false && tasks.begin()->first <= now)
{
todo = tasks.begin()->second;
tasks.erase(tasks.begin());
}
}
// Run tasks while unlocked so tasks can schedule new tasks
if (todo)
todo();
{
std::unique_lock<std::mutex> lock(mutex);
if (tasks.empty())
blocker.wait(lock);
else
blocker.wait_until(lock, tasks.begin()->first);
}
}
}
void ScheduleAt(std::chrono::system_clock::time_point & time, std::function<void()> func)
{
std::unique_lock<std::mutex> lock(mutex);
auto it = tasks.insert(std::make_pair(time, func));
if (it == tasks.begin())
blocker.notify_one();
}
void ScheduleEvery(std::chrono::system_clock::duration interval, std::function<void()> func)
{
std::function<void()> waitFunc = [this,interval,func]()
{
func();
this->ScheduleEvery(interval, func);
};
ScheduleAt(std::chrono::system_clock::now() + interval, waitFunc);
}
};
Notes:
I am not concerned with performance but I'll still take free lunches, like trading std::function
for another faster type.
I'm using VS2012 Express.
If a task takes time, other tasks may run belated. Such tasks should run in their own thread or be posted in an io_service or such. In any case, the caller (of ScheduleAt
or ScheduleEvery
) should take care of this.
Questions:
- What do you think?
- Are there drawbacks or pitfalls I did not see?
- Do other libs like Boost have something similar that I should use instead?
- Are there race conditions I missed?
- Is the destructor correct, especially the
bool
thing? I thought of usingvolatile
here but the Wikipedia article aboutvolatile
had me convinced otherwise.
todo = std::move(tasks.begin()->second)
to avoid copying the context \$\endgroup\$