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.

  1. ASIO shows how to use boost::bind with the Timers, but std::bind can be used in c++11 without modification
  2. I demonstrate using timers from inside cpp class instances, or global scopes.
  3. 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:

  1. Binding with std::bind the asio timer
  2. 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;
}

3 Comments on “Asio — Working with Timers

  1. 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);

Leave a Reply

Your email address will not be published. Required fields are marked *

*