Search code examples
c++multithreadingc++11timercallback

C++ callback timer with start/stop functions


I need to create a class to replicate the functionality of a callback timer from a library I no longer have access to.

Basically, spawn an asynchronous thread that executes some function after a timeout is reached. However, if some condition is met before the timeout, the function is not executed, and the timer is cancelled.

I am not sure how to "stop" the timer based on some condition.

This is what I have so far (based on the answer to this post C++ Non-blocking async timer by wally)

#include <iostream>
#include <chrono>
#include <thread>
#include <functional>
#include <mutex>
#include <condition_variable>

class Timer 
{
public:

   Timer(size_t time, const std::function<void(void)>& callback) : 
      time(std::chrono::milliseconds(time)),
      callback(callback) {}

   ~Timer() 
   { 
      if (started) 
      { 
         wait_thread.join(); 
         started = false; 
      } 
   }

   // TODO: Implement stop()
   void stop()
   {
      // ???
   }

   // TODO: Implement start()
   void start()
   {
      if (!started)
      {
         started = true;
         wait_thread = std::thread{ [this]() { wait_then_call(); } };
      }
      else
      {
         std::cout << "Thread " << wait_thread.get_id() << " already been started! " << std::endl;
      }
   }

private:

   void wait_then_call()
   {
      std::unique_lock<std::mutex> lck(mtx);
      std::cout << "Thread " << wait_thread.get_id() << " starting in " << time/1000 << " seconds" << std::endl;
      cv.wait_for(lck, time);

      callback();
   }

   std::mutex mtx;
   std::condition_variable cv{};
   std::chrono::milliseconds time;
   std::function <void(void)> callback;
   std::thread wait_thread;
   bool started = false;

};

Testing:

int main()
{
   auto f = []() {std::cout << "Executing Callback Function"; };

   Timer t1{ 3000, f };
   t1.start();

   Timer t2{ 10000, f };
   t2.start();

   // main thread is free to continue doing other things
}

Solution

  • I think the following meets all your requirements.

    Sample Code

    #include <condition_variable>
    #include <chrono>
    #include <iostream>
    #include <thread>
    
    template<class F>
    class DelayedExecution {
    public:
        DelayedExecution(std::chrono::milliseconds ms, F&& func)
            : ms_(ms)
            , func_(std::forward<F>(func)) {
        }
    
        ~DelayedExecution() {
            if (thread_.joinable())
                thread_.join();
        }
    
        void start() {
            thread_ = std::thread{ [this]() {
                std::unique_lock<std::mutex> lck(mutex_);
                cv_.wait_for(lck, ms_, [this]() { return stop_waiting_.load(); });
                if (not stop_waiting_)
                    func_();
            }};
        }
    
        void stop() {
            stop_waiting_.store(true);
            cv_.notify_one();
        }
    
    private:
        std::chrono::milliseconds ms_;
        F func_;
        std::thread thread_;
        std::condition_variable cv_;
        std::mutex mutex_;
        std::atomic<bool> stop_waiting_{false};
    };
    
    using std::cout, std::endl;
    
    int main(int argc, const char *argv[]) {
        DelayedExecution task1(std::chrono::milliseconds{250}, []() { cout << "task1" << endl; });
        DelayedExecution task2(std::chrono::milliseconds{250}, []() { cout << "task2" << endl; });
    
        task1.start();
        task2.start();
        task1.stop();
    
        return 0;
    }
    

    Output

    task2