Cum să creați evenimente de temporizare utilizând C++ 11? (Programare, C++, C++11)

Thiago R. Adams a intrebat.

Cum se creează evenimente de temporizare folosind C++ 11?

Am nevoie de ceva de genul: „Sună-mă după 1 secundă de acum încolo”.

Există vreo bibliotecă?

Comentarii

  • Ar trebui să includeți orice cod pe care l-ați încercat. –  > Por Miguel-F.
  • Vezi de exemplu acest răspuns vechi al meu. –  > Por Un tip programator.
  • Există o bibliotecă: boost.asio –  > Por Cubbi.
  • Mulțumesc Joachim. Asta este ceea ce căutam. Pentru că nu am acces la bucla principală (pentru a verifica cronometrele) va trebui să creez un thread. Voi folosi și un cronometru de înaltă rezoluție. Timpul meu de somn va fi foarte scurt (1 ms) și sper să nu folosesc prea mult CPU. –  > Por Thiago R. Adams.
  • Care este problema pe care o rezolvați în cazul în care aceasta este soluția dumneavoastră? –  > Por GManNickG.
6 răspunsuri
Edward A

A făcut o implementare simplă a ceea ce cred că este ceea ce doriți să obțineți. Puteți utiliza clasa later cu următoarele argumente:

  • int (milisecundele până la care să aștepți pentru a rula codul)
  • bool (dacă este adevărat se întoarce instantaneu și rulează codul după un timp specificat pe un alt fir de execuție)
  • Argumente variabile (exact ceea ce ați alimenta la std::bind)

Puteți modifica std::chrono::milliseconds în std::chrono::nanoseconds sau microseconds pentru o precizie și mai mare și să adăugați un al doilea int și o buclă for pentru a specifica de câte ori să executați codul.

Poftiți, bucurați-vă:

#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
", a);
    return;
}

int main()
{
    later later_test1(1000, false, &test1);
    later later_test2(1000, false, &test2, 101);

    return 0;
}

Ieșiri după două secunde:

101

Comentarii

  • O soluție foarte bună. Dar de ce nu doar o funcție în loc de o clasă? Preocuparea mea cu privire la async este cât de rapid este să pornească. Având un fir exclusiv care rulează doar pentru cronometre, poate că este mai rapid. Voi măsura și apoi voi posta din nou aici. –  > Por Thiago R. Adams.
  • @Thiago R. Adams Aveți dreptate, poate funcționa și ca o funcție, deși clasa vă permite să adăugați mai multe funcționalități la ea, așa cum am spus, acesta este doar un simplu exemplu. Puteți, de asemenea, să faceți un grup de fire de execuție cu atâtea fire de execuție cât numărul de nuclee (std::thread::hardware_concurrency()) și să distribuiți evenimentele cronometrate către acesta, etc. –  > Por Edward A.
  • Am făcut primul test. thradams.com/codeblog/timers.htm Am adormit firul care calculează timpul rămas până la următorul cronometru. Dacă se adaugă un anumit timer trezesc firul înainte. –  > Por Thiago R. Adams.
  • Din nefericire std::async apel nu va face ceea ce vă așteptați să facă. Din moment ce nu doriți nici un fel de alăturare, nu veți reuși să începeți o detașare std::thread în schimb: std::thread([after, task]() { std::this_thread::sleep_for(std::chrono::milliseconds(after)); task(); }).detach();. –  > Por Christian Rau.
  • Cum veți anula cronometrul? –  > Por Benji Mizrahi.
Thiago R. Adams

Acesta este codul pe care îl am până acum:

Folosesc VC++ 2012 (fără șabloane variadice).

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

Comentarii

  • Am găsit o problemă. Mutexul trebuie să fie deblocat înainte de TimerCall. –  > Por Thiago R. Adams.
eci

Soluția asincronă de la Edward:

  • creează un nou fir
  • dormiți în acel fir
  • executați sarcina în acel fir

este simplă și ar putea funcționa pentru tine.

Aș dori, de asemenea, să dau o versiune mai avansată care are aceste avantaje:

  • fără costuri de pornire a firelor
  • este necesar doar un singur fir suplimentar pentru fiecare proces pentru a gestiona toate sarcinile temporizate

Acest lucru ar putea fi util în special în cazul proiectelor software de mari dimensiuni, în care aveți multe sarcini executate în mod repetitiv în procesul dumneavoastră și vă interesează utilizarea resurselor (fire) și, de asemenea, durata de pornire.

Idee: aveți un singur fir de serviciu care procesează toate sarcinile cronometrate înregistrate. Utilizați boost io_service pentru aceasta.

Cod similar cu: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
"); });

  boost::asio::deadline_timer t2(io, boost::posix_time::seconds(1));
  t2.async_wait([](const boost::system::error_code& /*e*/){
    printf("Printed after 1s
"); });

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

zwcloud

Utilizați 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;});

Comentarii

  • Acesta este singurul care funcționează foarte bine pentru mine. Îl folosesc cu gRPC pentru a transmite răspunsurile în flux unidirecțional și asincron. Deocamdată nu există o modalitate idiomatică de a face acest lucru. –  > Por errolflynn.
tcb

Dacă sunteți pe Windows, puteți utiliza opțiunea CreateThreadpoolTimer pentru a programa un callback fără a fi nevoie să vă faceți griji cu privire la gestionarea firelor și fără a bloca firul curent.

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

    call_after(callback, 1000);
    std::cin.get();
}

Comentarii

  • Vă rugăm să specificați în răspuns că această soluție este numai pentru Windows. –  > Por marco.m.
  • Am încercat să compilez primul fragment de cod care folosește concurrency::timer folosind visual studio 2017 și am primit următoarea eroare: Eroare C3861 ‘link_target’: identifier not found Orice sugestie. –  > Por TrustyCoder.
  • Cum se anulează un temporizator? –  > Por Geob-o-matic.
Polar

Caut o soluție simplă și tot ce am găsit este prea lung și complicat. După ce am citit documentația, am descoperit că acest lucru se poate face în doar câteva linii de cod.

Această întrebare poate fi veche, dar poate fi benefică pentru viitorii cercetători.

Exemplu: Set isContinue la false dacă doriți să opriți firul.

#include <chrono>
#include <thread>

bool isContinue = true;

void NameOfYourFunction(){
  while(continue){
     std::this_thread::sleep_for(std::chrono::milliseconds(1000)); //sleep for 1 seconds
     //do something here after every 1 seconds...
  }
}

int main(){
  std::thread your_thread(NameOfYourFunction); // Register your `YourFunction`.
  your_thread.detach(); // this will be non-blocking thread.
  //your_thread.join(); // this will be blocking thread.
}

utilizați detach() sau join() în funcție de situația în care vă aflați.

  • Atunci când utilizați detach(), , firul principal de execuție continuă să funcționeze.
  • Când se utilizează join(), , firul principal de execuție se oprește și așteaptă până când noul fir se termină.

Tags:,