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 thecallback
. - 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 becausestd::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
Constructor Initialization VS Assignment
Why the Libc++ Std::Vector Internally Keeps Three Pointers Instead of One Pointer and Two Sizes
Who Defines C Operator Precedence and Associativity
Comma Operator in If Condition
Why Don't Std::Vector's Elements Need a Default Constructor
Comparing 3 Modern C++ Ways to Convert Integral Values to Strings
Address of Function Is Not Actual Code Address
What Does the Vertical Pipe ( | ) Mean in C++
C++: Fastest Method to Check If All Array Elements Are Equal
How to Avoid Undefined Execution Order for the Constructors When Using Std::Make_Tuple
Implicit Conversion When Overloading Operators for Template Classes
Signedness of Enum in C/C99/C++/C++X/Gnu C/Gnu C99
What Happens If a Constructor Throws an Exception
Align Cout Format as Table's Columns
How to Implement No-Op MACro (Or Template) in C++