Что эквивалентно Qtimer в С++ с использованием стандартных или повышающих библиотек?

Я должен выполнять какую-то задачу каждые 5 секунд, пока программа не выйдет. Я не хочу использовать нить здесь.

В QT я мог бы сделать так

QTimer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000);

но как мне сделать это на С++, используя библиотеки std или boost?

Спасибо


person Seeker    schedule 27.07.2018    source источник
comment
Возможный дубликат stackoverflow.com/questions/4267546 /   -  person L. Scott Johnson    schedule 27.07.2018
comment
Без использования какого-либо дополнительного потока считайте, что ваша программа всегда выполняется в потоке. Возможно, вы захотите попробовать std::this_thread::sleep_for(std::chrono::milliseconds(5000));   -  person AlexG    schedule 27.07.2018


Ответы (2)


Я должен предположить, что под «я не хочу использовать поток» вы имеете в виду, что не хотите создавать потоки в своем собственном коде каждый раз, когда вам нужен таймер. Это потому, что делать это без потоков на самом деле довольно сложно.

Предполагая C++11, вы действительно можете сделать это, используя просто базовый язык (без Boost или чего-то еще) и используя отдельный класс, обрабатывающий потоки, так что все, что вам нужно в вашем собственном коде, что-то вроде (например, преследование вашего бывшего партнера спамом по электронной почте, довольно сомнительный вариант использования):

    Periodic spamEx(std::chrono::seconds(60), SendEmaiToEx);

Следующая полная программа, скомпилированная с помощью g++ -std=c++11 -o periodic periodic.cpp -lpthread, будет запускать функцию обратного вызова каждую секунду в течение пяти секунд(a):

#include <thread>
#include <chrono>
#include <functional>
#include <atomic>

// Not needed if you take couts out of Periodic class.
#include <iostream>

class Periodic {
public:
    explicit Periodic(
        const std::chrono::milliseconds &period,
        const std::function<void ()> &func
    )
        : m_period(period)
        , m_func(func)
        , m_inFlight(true)
    {
        std::cout << "Constructing periodic" << std::endl;
        m_thread = std::thread([this] {
            while (m_inFlight) {
                std::this_thread::sleep_for(m_period);
                if (m _inFlight) {
                    m_func();
                }
            }
        });
    }

    ~Periodic() {
        std::cout << "Destructed periodic" << std::endl;
        m_inFlight = false;
        m_thread.join();
        std::cout << "Destructed periodic" << std::endl;
    }

private:
    std::chrono::milliseconds m_period;
    std::function<void ()> m_func;
    std::atomic<bool> m_inFlight;
    std::thread m_thread;
};

// This is a test driver, the "meat" is above this.

#include <iostream>

void callback() {
    static int counter = 0;
    std::cout << "Callback " << ++counter << std::endl;
}

int main() {
    std::cout << "Starting main" << std::endl;
    Periodic p(std::chrono::seconds(1), callback);
    std::this_thread::sleep_for(std::chrono::seconds(5));
    std::cout << "Ending main" << std::endl;
}

Когда вы создаете экземпляр Periodic, он сохраняет соответствующую информацию и запускает поток для выполнения работы. Поток (лямбда) - это просто цикл, который сначала задерживает на период, а затем вызывает вашу функцию. Он продолжает делать это до тех пор, пока деструктор не укажет, что он должен остановиться.

Вывод, как и ожидалось:

Starting main
Constructing periodic
Callback 1
Callback 2
Callback 3
Callback 4
Ending main
Destructed periodic

(a) Обратите внимание, что время, указанное выше, на самом деле является временем от конца одного обратного вызова до начала следующего, не время от начала до начала (что я бы называют истинным временем цикла). Если ваш обратный вызов достаточно быстр по сравнению с периодом, разница, надеюсь, будет незаметной.

Кроме того, поток делает эту задержку, несмотря ни на что, так что деструктор может быть задержан до полного периода перед возвратом.

Если вам действительно требуется период от начала до начала и быстрая очистка, вместо этого вы можете использовать следующую цепочку. Он выполняет точное время от начала до начала, определяя продолжительность обратного вызова и задерживая его только на остаток периода (или вообще не задерживаясь, если обратный вызов использовал весь период).

Он также использует меньший сон, чтобы очистка была быстрой. Функция потока будет:

m_thread = std::thread([this] {
    // Ensure we wait the initial period, then start loop.

    auto lastCallback = std::chrono::steady_clock::now();
    while (m_inFlight) {
        // Small delay, then get current time.

        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        auto timeNow = std::chrono::steady_clock::now();

        // Only callback if still active and current period has expired.

        if (m_inFlight && timeNow - lastCallback >= m_period) {
            // Start new period and call callback.

            lastCallback = timeNow;
            m_func();
        }
    }
});

Имейте в виду, что если ваш обратный вызов занимает больше времени, чем период, вы в основном будете вызывать его почти непрерывно (по крайней мере, будет разрыв в 100 мс).

person paxdiablo    schedule 27.07.2018

Вы понимаете, что QTimer использует поток или опрашивает таймер в основном цикле событий. Вы можете сделать то же самое. Концептуальная проблема, с которой вы, вероятно, столкнетесь, заключается в том, что у вас нет пользовательского интерфейса и, следовательно, вы, вероятно, не создали цикл обработки событий.

Вот самый простой способ использовать Boost Asio для создания цикла событий:

Прямой эфир на Coliru

#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <functional>
#include <chrono>
#include <iostream>

using namespace std::chrono_literals;
using boost::system::error_code;
namespace ba = boost::asio;

int main() {
    ba::io_service svc; // prefer io_context in recent boost versions
    ba::high_resolution_timer timer{svc};

    std::function<void()> resume;
    resume = [&] {
        timer.expires_from_now(50ms); // just for demo, don't wait 5s but 50ms

        timer.async_wait([=,&timer](error_code ec) {
            std::cout << "Timer: " << ec.message() << "\n";
            if (!ec) 
                resume();
        });
    };

    resume();
    svc.run_for(200ms); // probably getting 3 or 4 successful callbacks

    timer.cancel();
    svc.run(); // graceful shutdown
}

Отпечатки:

Timer: Success
Timer: Success
Timer: Success
Timer: Success
Timer: Operation canceled

Это может не иметь особого смысла в зависимости от остальной части вашего приложения. В таких случаях вы можете сделать то же самое, но использовать отдельный поток (да) для запуска этого цикла событий.

person sehe    schedule 27.07.2018