C++ Boost Asio Simple Periodic Timer

C++ Boost ASIO simple periodic timer?

The second example on Boosts Asio tutorials explains it.

You can find it here.

After that, check the 3rd example to see how you can call it again with a periodic time intervall

Create timer with boost::asio

Update I get it. You want dynamically allocated timers?

Live On Coliru

#include <boost/asio.hpp>
#include <boost/make_shared.hpp>

using namespace boost;
using Timer = shared_ptr<asio::deadline_timer>;

asio::io_service _io;

void handler(int a, const system::error_code& = {}, Timer timer = make_shared<Timer::element_type>(_io)) {
std::cout << "hello world\n";
timer->expires_from_now(posix_time::seconds(1));
timer->async_wait(bind(handler, a, asio::placeholders::error, timer));
}

int main() {
handler(42);
_io.run();
}

The trick is boost::bind binds to boost::shared_ptr and keep a copy of it - extending the lifetime of the timer object


A deadline timer, firing every second, and not using any globals:

#include <boost/asio.hpp>
#include <boost/thread.hpp>

using namespace boost;

int main() {

thread t([] {
asio::io_service io;
asio::deadline_timer dt(io, posix_time::seconds(1));
function<void(system::error_code)> ll = [&](system::error_code ec) {
if (!ec) {
puts("hello world");
dt.expires_from_now(posix_time::seconds(1));
dt.async_wait(ll);
}
};
ll({});
io.run();
});

this_thread::sleep_for(chrono::seconds(2));
std::cout << "async\n";

t.join();
}

Output:

Hello world
Hello world
async
Hello world
Hello world
....

Boost asio deadline timer completing immediately (C++)

As mentioned in the comments, deadline_timer is destroyed too soon because it's a local variable, thus canceling the I/O operation.

If we add some error handling, we will see the actual error reported:

void print(const boost::system::error_code& e) {
if (e.failed())
std::cout << "error: " << e.message() << std::endl;
else
std::cout << "connected!" << std::endl;
}

Prints:

error: The I/O operation has been aborted because of either a thread exit or an application request

A possible fix is to move deadline_timer to be member of WebSocketSession:

class WebSocketSession {
public:
WebSocketSession(boost::asio::io_context& io_context) : io_context_(io_context),
timer_(io_context, boost::posix_time::seconds(10)) {}
void connect() {
timer_.async_wait(&print);
}
private:
boost::asio::io_context& io_context_;
boost::asio::deadline_timer timer_;
};

C++ boost asynchronous timer to run in parallel with program

To deconstruct the task at hand, I'll start with a bare-bones C++98 implementation.

We'll clean it up to be modern C++, and then replace with Asio.

You will see that Asio doesn't require threading, which is nice. But we have to work back in time, replacing modern C++ with C++98.

In the end you will see all the reasons to join modern C++, as well as how to organize your code in such a way that you can easily manage the complexity.

C++98

Here's how I'd write that in c++98:

Live On Coliru

#include <pthread.h>
#include <iostream>
#include <sstream>
#include <unistd.h>

static pthread_mutex_t s_mutex = {};
static bool s_running = true;

static bool is_running(bool newvalue) {
pthread_mutex_lock(&s_mutex);
bool snapshot = s_running;
s_running = newvalue;
pthread_mutex_unlock(&s_mutex);
return snapshot;
}

static bool is_running() {
pthread_mutex_lock(&s_mutex);
bool snapshot = s_running;
pthread_mutex_unlock(&s_mutex);
return snapshot;
}

static void output(std::string const& msg) {
pthread_mutex_lock(&s_mutex);
std::cout << msg << "\n";
pthread_mutex_unlock(&s_mutex);
}

static void* count_thread_func(void*) {
for (int i = 0; i < 5; ++i) {
::sleep(1);
std::ostringstream oss;
oss << "COUNTER AT " << (i+1);
output(oss.str());
}
is_running(false);
return NULL;
}

int main() {
pthread_t thr = {0};
pthread_create(&thr, NULL, &count_thread_func, NULL);

while (is_running()) {
::usleep(200000);
output("TEST_ABC");
}

pthread_join(thr, NULL);
}

Prints

TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 1
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 2
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 3
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 4
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 5
TEST_ABC

C++11

Well the above really is hardly C++. It would actually be the "same" but more convenient in C with printf. Here's how C++11 improves things:

  • std::thread, std::atomic_bool, std::chono, std::this_thread, std::to_string, std::mutex/lock_guard, better initialization all around.

Live On Coliru

#include <thread>
#include <iostream>
#include <chrono>
#include <mutex>
#include <atomic>
using std::chrono::milliseconds;
using std::chrono::seconds;

static std::mutex s_mutex;
static std::atomic_bool s_running {true};

static void output(std::string const& msg) {
std::lock_guard<std::mutex> lk(s_mutex);
std::cout << msg << "\n";
}

static void count_thread_func() {
for (int i = 0; i < 5; ++i) {
std::this_thread::sleep_for(seconds(1));
output("COUNTER AT " + std::to_string(i+1));
}

s_running = false;
}

int main() {
std::thread th(count_thread_func);

while (s_running) {
std::this_thread::sleep_for(milliseconds(200));
output("TEST_ABC");
}

th.join();
}

Same output, but much more legible. Also, many more guarantees. We could have detached the thread with just th.detach(), or passed any arguments we want to the thread function, instead of the void* dance.

C++17


C++14 adds some more (chrono literals), C++17 only marginally (fold
expressions used here to have natural ostream-access):

Live On Coliru
only. Note this is down to 35 LoC

Back To C++1x: ASIO

Translating into ASIO removes the need for threads altogether, replacing the sleep with asynchronous timers.

Because there is no threading, there doesn't have to be any locking, simplifying life.

We don't need a "running" flag, because we can stop the service or cancel timers if we need to.

The entire program boils down to:

Since we will have to tasks running on an interval, let's put the mechanics for that in a simple class, so we don't have to repeat it:

// simple wrapper that makes it easier to repeat on fixed intervals
struct interval_timer {
interval_timer(boost::asio::io_context& io, Clock::duration i, Callback cb)
: interval(i), callback(cb), timer(io)
{}

void run() {
timer.expires_from_now(interval);
timer.async_wait([=](error_code ec) {
if (!ec && callback())
run();
});
}

void stop() {
timer.cancel();
}

private:
Clock::duration const interval;
Callback callback;
boost::asio::high_resolution_timer timer;
};

That looks pretty self-explanatory to me. The whole program now boils down to only:

int main() {
boost::asio::io_context io;

interval_timer abc { io, 200ms, [] {
std::cout << "TEST_ABC" << std::endl;
return true;
} };

interval_timer counter { io, 1s, [&abc, current=0]() mutable {
std::cout << "COUNTER AT " << ++current << std::endl;

if (current < 5)
return true;

abc.stop();
return false;
} };

abc.run();
counter.run();

io.run();
}

See it Live On Coliru.

We can simplify it a bit more if we use run_for to limit the execution (so we don't have to deal with exiting ourselves): Live On Coliru, down to 44 LoC

#include <boost/asio.hpp>
#include <iostream>
#include <chrono>
#include <functional>
using namespace std::chrono_literals;
using Clock = std::chrono::high_resolution_clock;
using Callback = std::function<void()>;
using boost::system::error_code;

// simple wrapper that makes it easier to repeat on fixed intervals
struct interval_timer {
interval_timer(boost::asio::io_context& io, Clock::duration i, Callback cb)
: interval(i), callback(cb), timer(io)
{ run(); }

private:
void run() {
timer.expires_from_now(interval);
timer.async_wait([=](error_code ec) {
if (!ec) {
callback();
run();
}
});
}

Clock::duration const interval;
Callback callback;
boost::asio::high_resolution_timer timer;
};

int main() {
boost::asio::io_context io;

interval_timer abc { io, 200ms, [] {
std::cout << "TEST_ABC" << std::endl;
} };

interval_timer counter { io, 1s, [current=0]() mutable {
std::cout << "COUNTER AT " << ++current << std::endl;
} };

io.run_for(5s);
}

Back to C++98

No lambda's. Okay, we can use boost::bind or just write some classes ourselves. You pick your poison, I chose a mixture:

  • boost::bind because it was the tool of that era (we're talking 20 years ago)
  • using virtual method instead of std::function for the callback.
  • The lambda captures have been replaced with explicit member variables.

It all becomes a lot less elegant, but basically recognizable as the same thing:

Live On Coliru

#include <boost/asio.hpp>
#include <iostream>
#include <boost/bind.hpp>
using boost::posix_time::seconds;
using boost::posix_time::millisec;
typedef boost::posix_time::microsec_clock Clock;
using boost::system::error_code;

// simple wrapper that makes it easier to repeat on fixed intervals
struct interval_timer {
interval_timer(boost::asio::io_context& io, millisec i)
: interval(i), timer(io)
{ run(); }

virtual bool callback() = 0;

void run() {
timer.expires_from_now(interval);
timer.async_wait(boost::bind(&interval_timer::on_timer, this, boost::asio::placeholders::error()));
}

void stop() {
timer.cancel();
}

private:
void on_timer(error_code ec) {
if (!ec && callback())
run();
}
millisec const interval;
boost::asio::deadline_timer timer;
};

int main() {
boost::asio::io_context io;

struct abc_timer : interval_timer {
abc_timer(boost::asio::io_context& io, millisec i) : interval_timer(io, i) {}
virtual bool callback() {
std::cout << "TEST_ABC" << std::endl;
return true;
}
} abc(io, millisec(200));

struct counter_timer : interval_timer {
counter_timer(boost::asio::io_context& io, millisec i, interval_timer& abc)
: interval_timer(io, i), abc(abc), current(0) {}

virtual bool callback() {
std::cout << "COUNTER AT " << ++current << std::endl;

if (current < 5)
return true;

abc.stop();
return false;
}
private:
interval_timer& abc;
int current;
} counter(io, millisec(1000), abc);

io.run();
}

The output is still the same trusty

TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 1
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 2
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 3
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 4
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
TEST_ABC
COUNTER AT 5

The same transformation as earlier with run_for can be applied here as well, but we now have to link Boost Chrono because std::chrono didn't exist: Live On Coliru, still 56 LoC

asio periodic timer

Your deadline timer constructor is different from the one in the example. You need to explicitly set the expiry time.
The example code uses the other constructor which sets a particular expiry time relative to now.

So the print-out you are seeing is related to your call to update, which calls

_timer.expires_at(_timer.expires_at() + boost::posix_time::milliseconds(1000));

and _timer.expires_at() has not been set yet...

Boost - periodic task scheduler

Analysis

The main culprit seems to be in the non-standard for each (auto task in tasks) (a Microsoft extension), which is basically equivalent of for (auto task : tasks). This means that you copy the elements of the tasks vector as you iterate over them, and work with the copy inside the loop body.

This becomes relevant in PeriodicTask::execute, specifically in

timer->async_wait(boost::bind(&PeriodicTask::execute, this));

where this points to the aforementioned copy, not the object stored in the vector.

We can add some simple debugging traces, to print the address of the objects in the vector as well as the address of the object on which execute is being invoked. Also reserve some space in the vector, so that no reallocations happen to simplify things.

When we run it, we'll see something like this in the console:

>example.exe
02-11-2016 20-04-36 created this=22201304
02-11-2016 20-04-36 created this=22201332
02-11-2016 20-04-36 execute this=19922484
02-11-2016 20-04-36 CPU usage
02-11-2016 20-04-36 execute this=19922484
02-11-2016 20-04-36 Memory usage
02-11-2016 20-04-46 execute this=19922484
02-11-2016 20-04-46 Memory usage
02-11-2016 20-04-46 execute this=19922484
02-11-2016 20-04-46 Memory usage
02-11-2016 20-04-46 execute this=19922484
02-11-2016 20-04-46 Memory usage
02-11-2016 20-04-46 execute this=19922484
.... and so on and on and on....

Let's analyse it a little. Let us assume that t refers to the starting time.

  • Line 1: Created CPU timer @ address 22201304, set to expire at t + 5 seconds.
  • Line 2: Created Memory timer @ address 22201332, set to expire at t + 10 seconds.
  • Lines 3,4: Made copy of CPU timer to address 19922484. Ran the handler. Scheduled CPU timer to run execute on object at address 19922484 at t + 5 + 5 seconds.
  • Lines 5,6: Made copy of Memory timer to address 19922484. Ran the handler. Scheduled Memory timer to run execute on object at address 19922484 in t + 10 + 10 seconds.

At this stage, we have two timers pending, one in 10 seconds, one in 20 seconds from startup. Both of them are scheduled to run member function execute on an object at address 19922484, which doesn't exist anymore at that point (it was a temporary in the for loop). By chance, the memory still contains the data from the last object that occupied that location -- the copy of the Memory task.

Time passes...

  • Lines 7,8: The CPU timer fires, and runs execute on object at address 19922484. As explained above, this means the method is running in context of the copy of the Memory task. Therefore we see "Memory usage" printed.

At this point, a timer is rescheduled. Due to our context, instead of rescheduling the CPU timer, we reschedule the still pending Memory timer. This causes the pending asynchronous wait operation to be cancelled, which will in turn result in the expiration handler being called and passed the error code boost::asio::error::operation_aborted. Your expiration handler, however, ignores the error codes. Thus

  • Lines 9,10: Cancellation triggers the Memory timer expiration handler, execute runs on object at address 19922484. As explained above, this means the method is running in context of the copy of the Memory task. Therefore we see "Memory usage" printed. There is already a pending asynchronous wait on the Memory timer, so we cause another cancellation when rescheduling.

  • Lines 11,12: Cancellation ... you get the gist.

Simple Fix

Change the for loop to use a reference.

for (auto& task : tasks) {
// ....
}

Console output:

>so02.exe
02-11-2016 20-39-30 created this=19628176
02-11-2016 20-39-30 created this=19628204
02-11-2016 20-39-30 execute this=19628176
02-11-2016 20-39-30 CPU usage
02-11-2016 20-39-30 execute this=19628204
02-11-2016 20-39-30 Memory usage
02-11-2016 20-39-40 execute this=19628176
02-11-2016 20-39-40 CPU usage
02-11-2016 20-39-45 execute this=19628176
02-11-2016 20-39-45 CPU usage
02-11-2016 20-39-50 execute this=19628176
02-11-2016 20-39-50 CPU usage
02-11-2016 20-39-50 execute this=19628204
02-11-2016 20-39-50 Memory usage
02-11-2016 20-39-55 execute this=19628176
02-11-2016 20-39-55 CPU usage

Further Analysis

We have fixed one small problem, however there are several other more or less serious issues with the code you presented.

A bad one is that you initialize a std::shared_ptr<boost::asio::io_service> with an address to already existing io_service instance (the member of PeriodicScheduler).

The code is in essence like:

boost::asio::io_service io_service;
std::shared_ptr<boost::asio::io_service> ptr1(&io_service);
std::shared_ptr<boost::asio::io_service> ptr2(&io_service);

which creates 3 owners of that object that don't know about each other.

Class PeriodicTask shouldn't be copyable -- it doesn't make sense, and would avoid the primary problem solved above. My guess would be that those shared pointers in it were an attempt to solve a problem with it being copied (and io_service being non-copyable itself).

Finally, the completion handler for the timer should have a boost::system::error_code const& parameter and at the least handle cancellation correctly.

Complete Solution

Let's start with includes, and a little convenience logging function.

#include <ctime>
#include <iostream>
#include <iomanip>
#include <functional>

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/noncopyable.hpp>

void log_text(std::string const& text)
{
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
std::cout << std::put_time(&tm, "%d-%m-%Y %H-%M-%S") << " " << text << std::endl;
}

Next, let's make PeriodicTask explicitly non-copyable and hold a reference to the io_service instance. This means we can avoid the other shared pointers as well. We can write a separate method to start the timer first time, and post it on the io_service, so that it's executed by run(). Finally, let's modify our completion handler to handle the error states, and behave correctly when cancelled.

class PeriodicTask : boost::noncopyable
{
public:
typedef std::function<void()> handler_fn;

PeriodicTask(boost::asio::io_service& ioService
, std::string const& name
, int interval
, handler_fn task)
: ioService(ioService)
, interval(interval)
, task(task)
, name(name)
, timer(ioService)
{
log_text("Create PeriodicTask '" + name + "'");
// Schedule start to be ran by the io_service
ioService.post(boost::bind(&PeriodicTask::start, this));
}

void execute(boost::system::error_code const& e)
{
if (e != boost::asio::error::operation_aborted) {
log_text("Execute PeriodicTask '" + name + "'");

task();

timer.expires_at(timer.expires_at() + boost::posix_time::seconds(interval));
start_wait();
}
}

void start()
{
log_text("Start PeriodicTask '" + name + "'");

// Uncomment if you want to call the handler on startup (i.e. at time 0)
// task();

timer.expires_from_now(boost::posix_time::seconds(interval));
start_wait();
}

private:
void start_wait()
{
timer.async_wait(boost::bind(&PeriodicTask::execute
, this
, boost::asio::placeholders::error));
}

private:
boost::asio::io_service& ioService;
boost::asio::deadline_timer timer;
handler_fn task;
std::string name;
int interval;
};

Let's have PeriodicScheduler keep a vector of unique_ptr<PeriodicTask>. Since PeriodicTask now handles getting started itself, we can simplify the run method. Finally, let's also make it non-copyable, since copying it doesn't really make much sense.

class PeriodicScheduler : boost::noncopyable
{
public:
void run()
{
io_service.run();
}

void addTask(std::string const& name
, PeriodicTask::handler_fn const& task
, int interval)
{
tasks.push_back(std::make_unique<PeriodicTask>(std::ref(io_service)
, name, interval, task));
}

private:
boost::asio::io_service io_service;
std::vector<std::unique_ptr<PeriodicTask>> tasks;
};

Now, let's put it all together and try it out.

int main()
{
PeriodicScheduler scheduler;

scheduler.addTask("CPU", boost::bind(log_text, "* CPU USAGE"), 5);
scheduler.addTask("Memory", boost::bind(log_text, "* MEMORY USAGE"), 10);

log_text("Start io_service");

scheduler.run();

return 0;
}

Console output:

>example.exe
02-11-2016 19-20-42 Create PeriodicTask 'CPU'
02-11-2016 19-20-42 Create PeriodicTask 'Memory'
02-11-2016 19-20-42 Start io_service
02-11-2016 19-20-42 Start PeriodicTask 'CPU'
02-11-2016 19-20-42 Start PeriodicTask 'Memory'
02-11-2016 19-20-47 Execute PeriodicTask 'CPU'
02-11-2016 19-20-47 * CPU USAGE
02-11-2016 19-20-52 Execute PeriodicTask 'CPU'
02-11-2016 19-20-52 * CPU USAGE
02-11-2016 19-20-52 Execute PeriodicTask 'Memory'
02-11-2016 19-20-52 * MEMORY USAGE
02-11-2016 19-20-57 Execute PeriodicTask 'CPU'
02-11-2016 19-20-57 * CPU USAGE
02-11-2016 19-21-02 Execute PeriodicTask 'CPU'
02-11-2016 19-21-02 * CPU USAGE
02-11-2016 19-21-02 Execute PeriodicTask 'Memory'
02-11-2016 19-21-02 * MEMORY USAGE

Re-using Boost's timer objects

You need to schedule the next timer event with

  • expires_at http://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/basic_deadline_timer/expires_at.html
  • or expires_from_now http://www.boost.org/doc/libs/1_66_0/doc/html/boost_asio/reference/basic_deadline_timer/expires_from_now.html

So, like:

#include <boost/asio.hpp>
#include <iostream>

void print(boost::system::error_code ec) {
static int i = 0;
if (ec != boost::asio::error::operation_aborted) {
i++;
}
std::cout << i << " (" << ec.message() << ")" << std::endl;
}

int main() {
boost::asio::io_service io;
boost::asio::deadline_timer t(io);

while (1) {
t.expires_from_now(boost::posix_time::seconds(1));
t.async_wait(&print);

if (io.stopped()) { io.reset(); }
io.run();
}
}

How to Awake at Certain Time in C++/Boost

You could use boost timers and always set the timer to run at the closest date of an alarm having to be triggered.
http://www.boost.org/doc/libs/1_36_0/doc/html/boost_asio/tutorial/tuttimer2.html

An example can also be found here
C++ Boost ASIO simple periodic timer?
In the stackOverflow example, the interval would be the minimum between all NextAlarmRun - currentTime.

In short

1) save all next alarm times

2) create timer and set to closest alarm time (A1)

3) timer is triggered, does stuff

4) change A1 to new value

5) calculate closest alarm time

6) reset timer to closest alarm time

Edit: In order to avoid calculating the closest alarm time every time, you can use an orderd list or vector and once an alarm is run, remove it and insert the new alarm time in the proper place in your structure. I wouldn't advise using a queue, since it might be possible that the current alarm to have to be scheduled before the last one in your queue.



Related Topics



Leave a reply



Submit