How to create timer events using C++ 11?

前端 未结 5 988
执念已碎
执念已碎 2020-11-27 02:24

How to create timer events using C++ 11?

I need something like: “Call me after 1 second from now”.

Is there any library?

相关标签:
5条回答
  • 2020-11-27 02:58

    This is the code I have so far:

    I am using VC++ 2012 (no variadic templates)

    //header
    #include <thread>
    #include <mutex>
    #include <condition_variable>
    #include <vector>
    #include <chrono>
    #include <memory>
    #include <algorithm>
    
    template<class T>
    class TimerThread
    {
      typedef std::chrono::high_resolution_clock clock_t;
    
      struct TimerInfo
      {
        clock_t::time_point m_TimePoint;
        T m_User;
    
        template <class TArg1>
        TimerInfo(clock_t::time_point tp, TArg1 && arg1)
          : m_TimePoint(tp)
          , m_User(std::forward<TArg1>(arg1))
        {
        }
    
        template <class TArg1, class TArg2>
        TimerInfo(clock_t::time_point tp, TArg1 && arg1, TArg2 && arg2)
          : m_TimePoint(tp)
          , m_User(std::forward<TArg1>(arg1), std::forward<TArg2>(arg2))
        {
        }
      };
    
      std::unique_ptr<std::thread> m_Thread;
      std::vector<TimerInfo>       m_Timers;
      std::mutex                   m_Mutex;
      std::condition_variable      m_Condition;
      bool                         m_Sort;
      bool                         m_Stop;
    
      void TimerLoop()
      {
        for (;;)
        {
          std::unique_lock<std::mutex>  lock(m_Mutex);
    
          while (!m_Stop && m_Timers.empty())
          {
            m_Condition.wait(lock);
          }
    
          if (m_Stop)
          {
            return;
          }
    
          if (m_Sort)
          {
            //Sort could be done at insert
            //but probabily this thread has time to do
            std::sort(m_Timers.begin(),
                      m_Timers.end(),
                      [](const TimerInfo & ti1, const TimerInfo & ti2)
            {
              return ti1.m_TimePoint > ti2.m_TimePoint;
            });
            m_Sort = false;
          }
    
          auto now = clock_t::now();
          auto expire = m_Timers.back().m_TimePoint;
    
          if (expire > now) //can I take a nap?
          {
            auto napTime = expire - now;
            m_Condition.wait_for(lock, napTime);
    
            //check again
            auto expire = m_Timers.back().m_TimePoint;
            auto now = clock_t::now();
    
            if (expire <= now)
            {
              TimerCall(m_Timers.back().m_User);
              m_Timers.pop_back();
            }
          }
          else
          {
            TimerCall(m_Timers.back().m_User);
            m_Timers.pop_back();
          }
        }
      }
    
      template<class T, class TArg1>
      friend void CreateTimer(TimerThread<T>& timerThread, int ms, TArg1 && arg1);
    
      template<class T, class TArg1, class TArg2>
      friend void CreateTimer(TimerThread<T>& timerThread, int ms, TArg1 && arg1, TArg2 && arg2);
    
    public:
      TimerThread() : m_Stop(false), m_Sort(false)
      {
        m_Thread.reset(new std::thread(std::bind(&TimerThread::TimerLoop, this)));
      }
    
      ~TimerThread()
      {
        m_Stop = true;
        m_Condition.notify_all();
        m_Thread->join();
      }
    };
    
    template<class T, class TArg1>
    void CreateTimer(TimerThread<T>& timerThread, int ms, TArg1 && arg1)
    {
      {
        std::unique_lock<std::mutex> lock(timerThread.m_Mutex);
        timerThread.m_Timers.emplace_back(TimerThread<T>::TimerInfo(TimerThread<T>::clock_t::now() + std::chrono::milliseconds(ms),
                                          std::forward<TArg1>(arg1)));
        timerThread.m_Sort = true;
      }
      // wake up
      timerThread.m_Condition.notify_one();
    }
    
    template<class T, class TArg1, class TArg2>
    void CreateTimer(TimerThread<T>& timerThread, int ms, TArg1 && arg1, TArg2 && arg2)
    {
      {
        std::unique_lock<std::mutex> lock(timerThread.m_Mutex);
        timerThread.m_Timers.emplace_back(TimerThread<T>::TimerInfo(TimerThread<T>::clock_t::now() + std::chrono::milliseconds(ms),
                                          std::forward<TArg1>(arg1),
                                          std::forward<TArg2>(arg2)));
        timerThread.m_Sort = true;
      }
      // wake up
      timerThread.m_Condition.notify_one();
    }
    
    //sample
    #include <iostream>
    #include <string>
    
    void TimerCall(int i)
    {
      std::cout << i << std::endl;
    }
    
    int main()
    {
      std::cout << "start" << std::endl;
      TimerThread<int> timers;
    
      CreateTimer(timers, 2000, 1);
      CreateTimer(timers, 5000, 2);
      CreateTimer(timers, 100, 3);
    
      std::this_thread::sleep_for(std::chrono::seconds(5));
      std::cout << "end" << std::endl;
    }
    
    0 讨论(0)
  • 2020-11-27 02:59

    Made a simple implementation of what I believe to be what you want to achieve. You can use the class later with the following arguments:

    • int (milliseconds to wait until to run the code)
    • bool (if true it returns instantly and runs the code after specified time on another thread)
    • variable arguments (exactly what you'd feed to std::bind)

    You can change std::chrono::milliseconds to std::chrono::nanoseconds or microseconds for even higher precision and add a second int and a for loop to specify for how many times to run the code.

    Here you go, enjoy:

    #include <functional>
    #include <chrono>
    #include <future>
    #include <cstdio>
    
    class later
    {
    public:
        template <class callable, class... arguments>
        later(int after, bool async, callable&& f, arguments&&... args)
        {
            std::function<typename std::result_of<callable(arguments...)>::type()> task(std::bind(std::forward<callable>(f), std::forward<arguments>(args)...));
    
            if (async)
            {
                std::thread([after, task]() {
                    std::this_thread::sleep_for(std::chrono::milliseconds(after));
                    task();
                }).detach();
            }
            else
            {
                std::this_thread::sleep_for(std::chrono::milliseconds(after));
                task();
            }
        }
    
    };
    
    void test1(void)
    {
        return;
    }
    
    void test2(int a)
    {
        printf("%i\n", a);
        return;
    }
    
    int main()
    {
        later later_test1(1000, false, &test1);
        later later_test2(1000, false, &test2, 101);
    
        return 0;
    }
    

    Outputs after two seconds:

    101
    
    0 讨论(0)
  • 2020-11-27 03:04

    If you are on Windows, you can use the CreateThreadpoolTimer function to schedule a callback without needing to worry about thread management and without blocking the current thread.

    template<typename T>
    static void __stdcall timer_fired(PTP_CALLBACK_INSTANCE, PVOID context, PTP_TIMER timer)
    {
        CloseThreadpoolTimer(timer);
        std::unique_ptr<T> callable(reinterpret_cast<T*>(context));
        (*callable)();
    }
    
    template <typename T>
    void call_after(T callable, long long delayInMs)
    {
        auto state = std::make_unique<T>(std::move(callable));
        auto timer = CreateThreadpoolTimer(timer_fired<T>, state.get(), nullptr);
        if (!timer)
        {
            throw std::runtime_error("Timer");
        }
    
        ULARGE_INTEGER due;
        due.QuadPart = static_cast<ULONGLONG>(-(delayInMs * 10000LL));
    
        FILETIME ft;
        ft.dwHighDateTime = due.HighPart;
        ft.dwLowDateTime = due.LowPart;
    
        SetThreadpoolTimer(timer, &ft, 0 /*msPeriod*/, 0 /*msWindowLength*/);
        state.release();
    }
    
    int main()
    {
        auto callback = []
        {
            std::cout << "in callback\n";
        };
    
        call_after(callback, 1000);
        std::cin.get();
    }
    
    0 讨论(0)
  • 2020-11-27 03:05

    Use RxCpp,

    std::cout << "Waiting..." << std::endl;
    auto values = rxcpp::observable<>::timer<>(std::chrono::seconds(1));
    values.subscribe([](int v) {std::cout << "Called after 1s." << std::endl;});
    
    0 讨论(0)
  • 2020-11-27 03:06

    The asynchronous solution from Edward:

    • create new thread
    • sleep in that thread
    • do the task in that thread

    is simple and might just work for you.

    I would also like to give a more advanced version which has these advantages:

    • no thread startup overhead
    • only a single extra thread per process required to handle all timed tasks

    This might be in particular useful in large software projects where you have many task executed repetitively in your process and you care about resource usage (threads) and also startup overhead.

    Idea: Have one service thread which processes all registered timed tasks. Use boost io_service for that.

    Code similar to: http://www.boost.org/doc/libs/1_65_1/doc/html/boost_asio/tutorial/tuttimer2/src.html

    #include <cstdio>
    #include <boost/asio.hpp>
    #include <boost/date_time/posix_time/posix_time.hpp>
    
    int main()
    {
      boost::asio::io_service io;
    
      boost::asio::deadline_timer t(io, boost::posix_time::seconds(1));
      t.async_wait([](const boost::system::error_code& /*e*/){
        printf("Printed after 1s\n"); });
    
      boost::asio::deadline_timer t2(io, boost::posix_time::seconds(1));
      t2.async_wait([](const boost::system::error_code& /*e*/){
        printf("Printed after 1s\n"); });
    
      // both prints happen at the same time,
      // but only a single thread is used to handle both timed tasks
      // - namely the main thread calling io.run();
    
      io.run();
    
      return 0;
    }
    
    0 讨论(0)
提交回复
热议问题