Many deadline_timers

We have seen that using a single ASIO deadline_timer is easy, even though, as we have seen when we were talking about canceling a scheduled deadline_timer there are a few details that we have to consider carefully.

Working with a collection of deadline_timers does not add much more complexity, but it is worthy to have a look at an example, just to clarify a few details.

We want to give the users a way of printing strings (as many as they want) each of them in a specified moment. Here we are not interested in how these data are provided to the application, let's just fake the input using this function:
typedef std::pair<int, std::string> Job; // 1.
typedef std::vector<Job> Jobs;

Jobs createJobCollection()
{
Jobs jobs;

jobs.push_back(std::make_pair(4, "Do something"));
jobs.push_back(std::make_pair(2, "Say hallo"));
jobs.push_back(std::make_pair(5, "Say goodbye"));
jobs.push_back(std::make_pair(3, "Turn off the radio"));

return jobs; // 2.
}

1. Our job is actually a pair consisting in a delay an the string we want to output.
2. Your STL implementation should be smart enough to optimize this return statement using the RValue semantic, if not, you could explicitely use std::move() to avoid the overhead of meaningless copies.

To manage the deadline_timer collection execution I'm going to use a specific class, JobLauncher. Here is its declaration:
typedef boost::shared_ptr<boost::asio::deadline_timer> SmartDT; // 1.
class JobLauncher
{
private:
boost::asio::io_service ios_; // 2.
std::vector<SmartDT> dts_;
public:
void add(Jobs& jobs);
void run() { ios_.run(); }
};

1. deadline_timer is a non-copyable class, but we want to have a STL collection of them. So we use smart pointers instead.
2. Remember that the order in which member variables are listed in a class declaration is the order used by the compiler to construct them in memory (and the reverse order used for destruct them). So we don't have to do anything more than writing the two data member in the correct order to ensure the relation between ASIO I/O Service and its associated Deadline Timers be enforced. On the other side, if we want to have a spectacular and unexpected crash, we could just invert these two lines.

We still have to define one function:
void JobLauncher::add(Jobs& jobs)
{
std::for_each(jobs.begin(), jobs.end(), // 1.
[&](Job j) // 2.
{
SmartDT dt(new boost::asio::deadline_timer(ios_)); // 3.

dt->expires_from_now(boost::posix_time::seconds(j.first));
dt->async_wait(boost::bind(&doJob, _1, j.second));
dts_.push_back(dt);
});
}

1. For each element in the passed collection of jobs we have to add a deadline timer to the member collection. We could do that in a number of ways. For instance we could use a STL standard algorithm for_each associated with a lambda function. There is another post where I chat about for each and alternatives.
2. We need to access a couple of variable outside the lambda scope, the ampersand in the lambda introducer (AKA capture clause - the open/close square brackets) says that we want to access them by reference. Actually, since here we are interested to member variable, that are mediate by "this" pointer, we could have used the equal sign instead, that means use the variable by copy.
3. A smart pointer for deadline_timer is created - notice that references to the member ASIO I/O Service.

And here is a simple tester:
Jobs jobs = createJobCollection();
JobLauncher jl;
jl.add(jobs);
jl.run();

2 comments:

  1. Thanks for the great example. However, I do have the following compilation errors in the for_each statement above:

    error: expected primary-expression before '[' token
    error: expected primary-expression before ']' token
    error: expected primary-expression before 'j'

    ReplyDelete
  2. Hi, thank you for the feedback.

    It smells like your compiler does not support C++0x lambda function. I guess your best chance is rewriting the for_each loop using Boost lambda or a functor.

    ReplyDelete