3

I have to perform some task every 5 seconds till the program exits. I don't want to use a thread here.

In QT I could do like this

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

but how do I do this in c++ using std or boost libraries?

Thank you

Seeker
  • 101
  • 2
  • 10
  • Possible duplicate of https://stackoverflow.com/questions/4267546/c-boost-asio-simple-periodic-timer – L. Scott Johnson Jul 27 '18 at 01:40
  • Without using any additional thread, consider your program always run in a thread. You might want to try std::this_thread::sleep_for(std::chrono::milliseconds(5000)); – AlexG Jul 27 '18 at 02:09

2 Answers2

5

I have to assume that, by "I don't want to use a thread", you mean you don't want to create threads in your own code every time you need a timer. That's because doing it without threads is actually quite hard.

Assuming C++11, you can actually do this with just the core language (no Boost or any other stuff needed) and using a separate class handling the threading so that all you need in your own code is something like (for example, harassing your ex partner with spam emails, a rather dubious use case):

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

The following complete program, compiled with g++ -std=c++11 -o periodic periodic.cpp -lpthread will run a periodic callback function every second for five seconds(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;
}

When you create an instance of Periodic, it saves the relevant information and starts a thread to do the work. The thread (a lambda) is simply a loop which first delays for the period then calls your function. It continues to do this until the destructor indicates it should stop.

The output is, as expected:

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

(a) Note that the time given above is actually the time from the end of one callback to start of the next, not the time from start to start (what I would call true cycle time). Provided your callback is sufficiently quick compared to the period, the difference will hopefully be unnoticable.

In addition, the thread does this delay no matter what, so the destructor may be delayed for up to a full period before returning.

If you do require a start-to-start period and fast clean-up, you can use the following thread instead. It does true start-to-start timing by working out the duration of the callback and only delaying by the rest of the period (or not delaying at all if the callback used the entire period).

It also uses a smaller sleep so that clean-up is fast. The thread function would be:

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();
        }
    }
});

Be aware that, if your callback takes longer than the period, you will basically be calling it almost continuously (there'll be a 100ms gap at least).

paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
1

You realize that QTimer does use a thread - or polls the timer in the main event loop. You can do the same. The conceptual problem you're likely having is that you don't have a UI and therefore, probably didn't create an event loop.

Here's the simplest way to leverage Boost Asio to have an event loop:

Live On 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
}

Prints:

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

That may not make too much sense depending on the rest of your application. In such cases, you can do the same but use a separate thread (yes) to run that event loop.

sehe
  • 374,641
  • 47
  • 450
  • 633