Странное поведение таймера из boost::asio

В документации по бусту есть такой пример кода:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

using namespace boost;

void print(const system::error_code& e, asio::steady_timer* timer, int count)
{
    if (count < 5)
    {
        std::cout << count << std::endl;
        ++count;
        timer->expires_at(timer->expiry() + asio::chrono::seconds(1));
        timer->async_wait(boost::bind(print, asio::placeholders::error, timer, count));
    }
}

int main()
{
    asio::io_context io;
    int count = 0;
    asio::steady_timer t(io, asio::chrono::seconds(1));
    t.async_wait(bind(print, asio::placeholders::error, &t, count));
    io.run();
    return 0;
}

Здесь, в функции main, таймеру задается начальное значение 5 секунд:

asio::steady_timer t(io, asio::chrono::seconds(5));

ДЛя чего нужно это начальное значение? Я заметил, что если этот параметр убрать, то есть сделать так:

asio::steady_timer t(io);

то по таймеру мгновенно выводятся все значения от 0 до 5, а не раз в секунду. Почему такое поведение? Почему для вывода значений раз в секунду должно быть начально значение таймера?


Ответы (1 шт):

Автор решения: user7860670

У таймеров есть три вида конструкторов:

  1. Принимающие абсолютное время срабатывания.
  2. Принимающие относительное время срабатывания (относительно текущего).
  3. Не задающие время. Подразумевается, что время срабатывания будет задано потом.

asio::steady_timer t(io, asio::chrono::seconds(5)); относится ко второй категории, а asio::steady_timer t(io); - к третьей, однако время срабатывания вы не устанавливаете, оно остается равно началу отсчета часов, поэтому получается, что он истек уже в момент создания. Прибавляя к началу отсчета часов (вместо текущего времени) по секунде при вызове обработчика тоже получаются уже истекшие точки времени. Более наглядно происходящее можно понять, если написать обработчик нормально:

#include <boost/asio.hpp>
#include <boost/bind.hpp>

#include <chrono>
#include <iostream>

void print(::boost::system::error_code const & ec, ::boost::asio::steady_timer* timer, int count)
{
    auto const when{::std::chrono::time_point_cast<::std::chrono::seconds>(timer->expires_at()).time_since_epoch()};
    auto const now{::std::chrono::time_point_cast<::std::chrono::seconds>(::std::chrono::steady_clock::now()).time_since_epoch()};
    ::std::cout << "invoked at " << now << ", target " << when << "\n";
    if (ec)
    {
        if (::boost::asio::error::operation_aborted == ec)
        {
            ::std::cout << "timer cancelled\n";
        }
        else
        {
            ::std::cout << "timer error " << ec << "\n";
        }
    }
    else
    {
        if (when <= now)
        {
            ::std::cout << "timeout\n";
            std::cout << count << "\n";
            ++count;
            if (count < 5)
            {
                timer->expires_at(timer->expiry() + ::boost::asio::chrono::seconds{1});
                timer->async_wait(::boost::bind(&::print, ::boost::asio::placeholders::error, timer, count));
            }
        }
        else
        {
            ::std::cout << "misfire\n";
        }
    }
}

int main()
{
    ::boost::asio::io_context io{};
    int count{};
    ::boost::asio::steady_timer t{io/*, ::boost::asio::chrono::seconds{1}*/};
    t.async_wait(::boost::bind(&::print, ::boost::asio::placeholders::error, &t, count));
    io.run();
    return 0;
}

invoked at 8969s, target 0s
timeout
0
invoked at 8969s, target 1s
timeout
1
invoked at 8969s, target 2s
timeout
2
invoked at 8969s, target 3s
timeout
3
invoked at 8969s, target 4s
timeout
4

online compiler

→ Ссылка