I want the second timer to start working half a second after the second one, but the moment the second timer starts working, the terminal displays something completely different from what I want. If it does not impose restrictions before executing the second timer, then they both work as they should
#include <iostream>
#include <functional>
#include <boost/asio.hpp>
class print{
public:
print(boost::asio::io_context& io) : timer_(io, boost::asio::chrono::seconds(1)), count_(0){
timer_.async_wait(std::bind(&print::printer, this));
}
void printer(){
if(count_ < 5){
std::cout << count_ << "\n";
++count_;
timer_.expires_at(timer_.expiry() + boost::asio::chrono::seconds(1));
timer_.async_wait(std::bind(&print::printer, this));
}
}
~print(){
std::cout << "Last num " << count_ << "\n";
}
private:
boost::asio::steady_timer timer_;
int count_;
};
int main(){
boost::asio::io_context io;
print p(io);
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(2));
t.async_wait([&io](const boost::system::error_code& ec){
if(!ec){
print pp(io);
}
});
io.run();
}
that's what the terminal outputs
0,
1,
Last num 0,
2,
3,
4,
Last num 5.
The second timer pp
is a local variable. It is destructed immediately.
Therefore the async operation is using a deleted instance and invoked Undefined Behaviour.
You should probably be detecting cancellation by actually receiving error_code
in the completion handler. The std::bind
expression silently drops it for you, now.
You can use dynamic allocation of some sort. I also strongly suggest separating and generalizing the notion of an interval timer and the action that is executed:
struct Timer {
using Callback = std::function<bool()>;
Timer(asio::any_io_executor ex, duration d, Callback cb)
: timer_(ex, d)
, interval_(d)
, cb_(std::move(cb)) { do_loop(); }
private:
asio::steady_timer timer_;
duration interval_;
Callback cb_;
void do_loop() {
timer_.async_wait(bind(&Timer::on_tick, this, std::placeholders::_1));
}
void on_tick(error_code ec) {
if (ec) {
std::cerr << "Warning: " << ec.message() << std::endl;
return;
} else if (cb_()) {
timer_.expires_at(timer_.expiry() + interval_);
do_loop();
}
}
};
Now you can use it like:
#include <boost/asio.hpp>
#include <functional>
#include <iostream>
namespace asio = boost::asio;
using namespace std::chrono_literals;
using boost::system::error_code;
using duration = std::chrono::steady_clock::duration;
struct Timer {
using Callback = std::function<bool()>;
Timer(asio::any_io_executor ex, duration d, Callback cb)
: timer_(ex, d)
, interval_(d)
, cb_(std::move(cb)) { do_loop(); }
private:
asio::steady_timer timer_;
duration interval_;
Callback cb_;
void do_loop() {
timer_.async_wait(bind(&Timer::on_tick, this, std::placeholders::_1));
}
void on_tick(error_code ec) {
if (ec) {
std::cerr << "Warning: " << ec.message() << std::endl;
return;
} else if (cb_()) {
timer_.expires_at(timer_.expiry() + interval_);
do_loop();
}
}
};
int main() {
asio::io_context io;
auto ex = io.get_executor();
auto printer = [](std::string_view name) {
return [name, count_ = 0]() mutable {
bool more = count_ < 5;
std::cout << "Printer " << quoted(name) << (more ? " " : " Last num ") << count_ << "\n";
++count_;
return more;
};
};
std::optional<Timer> second;
Timer first(ex, 1s, printer("First"));
Timer delay(ex, 1500ms, [&] {
second.emplace(ex, 1s, printer("Second"));
return false;
});
io.run();
}
See it Live On Coliru
Which prints:
Printer "First" 0
Printer "First" 1
Printer "Second" 0
Printer "First" 2
Printer "Second" 1
Printer "First" 3
Printer "Second" 2
Printer "First" 4
Printer "Second" 3
Printer "First" Last num 5
Printer "Second" 4
Printer "Second" Last num 5
Or more interactively: