How to install a reoccurring timer function?

2019-06-09 03:46发布

问题:

Is there a simple way to install a regularly occurring timer function with C++/stdlib? I would like to get rid of the loop:

using namespace std::chrono; // literal suffixes
auto tNext = steady_clock::now();
while (<condition>) {
    std::this_thread::sleep_until(tNext);
    tNext = tNext + 100ms; 
    ...

That function will run in its own thread.

回答1:

I'm guessing what you want is this

int i = 10;
auto pred = [i]() mutable {return i--;};
auto print = []{cout << "." << endl;};

timer t{500ms};
t.push({print, pred});  //asynchronously prints '.' 10 times within 5s

//do anything else

Assuming performance is not critical and the timer is not often updated, the following should provide ample functionality.

#include<functional>
#include<vector>
#include<thread>
#include<utility>
#include<chrono>
#include<mutex>
#include<atomic>

class timer final
{
public:
    using microseconds = std::chrono::microseconds;

    using predicate = std::function<bool ()>;
    using callback = std::function<void ()>;
    using job = std::pair<callback, predicate>;

    explicit timer(microseconds t) : done{false}, period{t}
    {
        std::lock_guard<std::mutex> lck(mtx);

        worker = std::thread([this]{
            auto t = std::chrono::steady_clock::now();
            while(!done.load())
            {
                std::this_thread::sleep_until(t);
                std::lock_guard<std::mutex> lck(mtx);
                t += period;
                for(auto it = jobs.begin(); it != jobs.end();)
                {
                    if(it->second())
                        it++->first();
                    else
                        it = jobs.erase(it);
                }
            }
        });
    }

    ~timer()
    {
        done.store(true);
        worker.join();
    }

    void set_period(microseconds t)
    {
        std::lock_guard<std::mutex> lck(mtx);
        period = t;
    }
    void push(const callback& c)
    {
        std::lock_guard<std::mutex> lck(mtx);
        jobs.emplace_back(c, []{return true;});
    }
    void push(const job& j)
    {
        std::lock_guard<std::mutex> lck(mtx);
        jobs.push_back(j);
    }

private:
    std::mutex mtx;
    std::atomic_bool done;
    std::thread worker;

    std::vector<job> jobs;
    microseconds period;
};

timer calls previously pushed callbacks periodically, and when predicate evaluates to false, deletes the callback from the timer. The timer object has its own lifetime and its worker thread will only live as long as it is alive.

The reason you would want to have multiple jobs in a single timer is so that they would be called together, using only one thread and be in sync with each other.

Don't worry about the mutex unless you are planning to update the timer >10,000 times a second, have a period of <1ms or have very time-consuming callbacks.