Asio — Working with Timers
Asio C++ Library
Asio is a cross-platform C++ library for network and low-level I/O programming that provides developers with a consistent asynchronous model using a modern C++ approach.
I am using it specifically for asynchronous timers. I have discovered a couple concepts that are outside the scope of the ASIO documentation
I use timers with the mindset that I plan on resetting the timer under certain circumstances, so that the timer only expires if the timer does not get reset from certain events.
- ASIO shows how to use boost::bind with the Timers, but std::bind can be used in c++11 without modification
- I demonstrate using timers from inside cpp class instances, or global scopes.
- How to use std::thread instead of boost’s to create a threadpool for the io_service. This will not work with std::thread, but can with the power of c++ 11 lamda functons. see this StackOverflow post
Something that will not change in either example is the creation of the thread pool with std::thread
std::bind threadpool for asio::io_service
int main(int argc, char* argv[]) { try { // ************************** //// INIT // ************************** asio::io_service io_service; asio::io_service::work work(io_service); std::vector threadPool; for(size_t t = 0; t < std::thread::hardware_concurrency()*2; t++) { threadPool.push_back( std::thread([&io_service]() { io_service.run(); })); } // ************************** //// Program Code goes here // ************************** // ************************** //// Cleanup // ************************** io_service.stop(); for(std::thread& t : threadPool) { t.join(); } } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; }
Timers in Global scope
You only need to bind when the timer callback function requires parameters. And in our pretext that the timer must be reset, well the technical behavior must be understood — the asio timer, when cancelled, or given a new time (which effectively cancels the current timer), will cause the timer event to trigger. I know, it seems kind of odd that the timer event would trigger if you are canceling it! But it’s the way it goes, and I suppose that makes library coding easier ;) So we need to make sure the timer event is a TRUE timer trigger, and not just a cancel. The following code demonstrates:
- Binding with std::bind the asio timer
- in the timer callback, differentiating between a true timer event and just a cancel.
#include <iostream> // std::cout #include <thread> // std::thread #include <functional> // std::bind // ASIO #include "asio/deadline_timer.hpp" // Cannot pass by reference (&) // Least, I could not find out that method void check_deadline(asio::deadline_timer *deadline_) { // Check whether the deadline has passed. We compare the deadline against // the current time since a new asynchronous operation may have moved the // deadline before this actor had a chance to run. if (deadline_->expires_at() <= asio::deadline_timer::traits_type::now()) { std::cout << "the deadline has passed" << std::endl; } else { std::cout << "The deadline has NOT passed " << std::endl; } } int main(int argc, char* argv[]) { try { asio::io_service io_service; asio::io_service::work work(io_service); std::vector threadPool; asio::deadline_timer deadline_(io_service); // you can use std::thread::hardware_concurrency() to find the // number of hardware threads available for(size_t t = 0; t < 2; t++) { threadPool.push_back( std::thread([&io_service]() { io_service.run(); } ) ); } deadline_.expires_from_now(boost::posix_time::seconds(2)); deadline_.async_wait(std::bind(&check_deadline, &deadline_)); while (1) { std::string request; std::getline(std::cin, request); if (request == "q") break; // cancelling happens by way of expires_from_now // so we don't have to explicity call deadline_.cancel(); deadline_.expires_from_now(boost::posix_time::seconds(2)); deadline_.async_wait(std::bind(&check_deadline, &deadline_)); } std::cout << "Quitting" << std::endl; io_service.stop(); for(std::thread& t : threadPool) { t.join(); } } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; }
Timers in Class Scope
In this example, I also include a utility in the class that allows us to measure the actual time it is taking for the event to trigger from start.
#include <iostream> // std::cerr #include <thread> // std::thread #include <functional> // std::bind // ASIO #include "asio/deadline_timer.hpp" class MyClass { public: MyClass(asio::io_service& io_service, int num) : deadline_(io_service), num(num) { // Init the time base for taking relative time measurements timeval tv; gettimeofday(&tv, NULL); time_base = tv.tv_sec; } void check_deadline() { // Check whether the deadline has passed. We compare the deadline against // the current time since a new asynchronous operation may have moved the // deadline before this actor had a chance to run. if (deadline_.expires_at() <= asio::deadline_timer::traits_type::now()) { stop_time = get_ts(); total_time = stop_time - start_time; std::cerr << "the deadline " << num << " has passed after " << total_time << "ms" << std::endl; // don't think we need the next line //deadline_->expires_at(boost::posix_time::pos_infin); } else { std::cerr << "The deadline has NOT passed " << std::endl; // asio example does the following, but I find more power in doing // this call externally //deadline_->async_wait(boost::bind(&check_deadline, deadline_)); } } void start_timer(int sec) { std::cerr << "Resetting timer " << num << std::endl; deadline_.expires_from_now(boost::posix_time::seconds(sec)); deadline_.async_wait(std::bind(&MyClass::check_deadline, this)); start_time = get_ts(); } uint32_t get_ts() { uint32_t ts; struct timeval tv; gettimeofday(&tv, NULL); ts = ((tv.tv_sec - time_base) * 1000) + (tv.tv_usec / 1000); return(ts); } // identify this instance int num; asio::deadline_timer deadline_; uint32_t time_base, start_time, stop_time, total_time; }; int main(int argc, char* argv[]) { try { asio::io_service io_service; std::vector<std::thread> threadPool; asio::io_service::work work(io_service); MyClass mc1(io_service,1), mc2(io_service,2); for(size_t t = 0; t < std::thread::hardware_concurrency()*2; t++) { threadPool.push_back( std::thread([&io_service]() { io_service.run(); })); } mc1.start_timer(2); mc2.start_timer(5); while (1) { std::string request; std::getline(std::cin, request); if (request == "q") break; mc1.start_timer(2); } std::cerr << "Quitting" << std::endl; io_service.stop(); for(std::thread& t : threadPool) { t.join(); } } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << "\n"; } return 0; }
test 1 2.
In your second example (Timers in Global scope), you added extra code to check, whether the timer has expired. But this can be achieved much easier, but the timeout callback needs a different signature:
void timeer_cb(const boost::system::error_code &ec)
{
if (ec)
{
std::cout << "The deadline has NOT passed " << std::endl;
}
else
{
std::cout << "the deadline has passed" << std::endl;
}
}
When using std::bind, you need to add a placeholder for the parameter:
std::bind(&timeer_cb, std::placeholders::_1);
Thanks! This was a useful example for me