具有启动/停止函数的 C++ 回调计时器

C++ callback timer with start/stop functions

提问人:Dominic Sesto 提问时间:5/19/2023 最后编辑:Dominic Sesto 更新时间:5/19/2023 访问量:435

问:

我需要创建一个类来复制我不再有权访问的库中的回调计时器的功能。

基本上,生成一个异步线程,该线程在达到超时后执行某些函数。但是,如果在超时之前满足某些条件,则不会执行该函数,并且计时器将被取消。

我不确定如何根据某些条件“停止”计时器。

这就是我到目前为止所拥有的(基于这篇文章的答案 C++ 非阻塞异步计时器 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;

};

测试:

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
}
C++ 多线程 C++11 计时器 回调

评论

0赞 Sam Varshavchik 5/19/2023
所示代码使用 .有一个重载,它采用第三个参数,即谓词。通常,此“条件”将根据在持有线程正在等待()的相同锁时设置的标志来定义,因此,当计时器过期或满足谓词时,合适的谓词将终止,并且额外的检查将确定哪种情况。任务完成。任何关于高级多线程的 C++ 教科书都将提供更完整的技术细节。wait_forwait_forwait_for()
0赞 user4581301 5/19/2023
您需要进行相当多的重写,以获得实际停止计时器的功能。另一方面,如果你设置了一个标志,检查它是否应该跟进并拨打电话,那么这项工作就很容易了。stopwait_then_call

答:

0赞 RandomBits 5/19/2023 #1

我认为以下内容符合您的所有要求。

示例代码

#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;
}

输出

task2

评论

0赞 Dominic Sesto 5/19/2023
如果我需要多次启动/停止定时器怎么办?
0赞 user4581301 5/19/2023
这是一个纯代码答案。幸运的是,通过加入合理的解释量(在这种情况下很少)可以很容易地解决这个问题。
0赞 user4581301 5/19/2023
@DominicSesto 将 stop_waiting_' 设置为开头,您应该能够随心所欲地重复使用......只要您确保正在运行的线程有时间确认条件变量并退出即可。如果没有更多的基础设施,这可能很难保证。falsestart
0赞 RandomBits 5/19/2023
@DominicSesto 当您多次说启动和停止计时器时,计时器应该在每次停止时重置,还是希望它以相同的倒计时继续?重置是一个很小的更改,但能够打开和关闭倒计时需要重新考虑,因为当前解决方案中没有任何内容跟踪用完的时间。
0赞 RandomBits 5/19/2023
@DominicSesto 此外,允许计时器在停止后重新启动会引入同步问题。由于是非阻塞的,它将立即返回,客户端线程无法知道何时可以安全地重新启动计时器。而且,如果在设置标志之前调用函数并调用该函数怎么办? 需要返回函数是否被调用的指示。而且,如果您重新启动计时器,是否应该再次调用它?这需要更详细地说明它应该如何表现。stopstopstopstop