03-Timer.3-Binding arguments to a completion handler

在本教程中,我们将修改教程Timer.2中的程序,使计时器每秒触发一次。这将展示如何向处理函数传递额外参数。

#include <asio.hpp>
#include <iostream>
#include <spdlog/spdlog.h>

// 要使用asio实现一个重复计时器,你需要在完成处理程序中修改计时器的到期时间,然后启动一个新的异步等待。
// 显然,这意味着完成处理程序需要能够访问计时器对象。为此,我们在print函数中添加了两个新参数:
//• 一个指向计时器对象的指针;
//• 以及一个计数器,以便在计时器第6次触发时停止程序
void print(const std::error_code& error, asio::steady_timer* t, int* count)
{
    if (error) {
        spdlog::error("Error[{}]: {}", error.value(), error.message());
    }

    // 如上所述,本教程程序使用计数器在定时器第六次触发时停止运行。
    // 但您会注意到,这里并未显式调用io_context的停止指令。
    // 回顾教程Timer.2的内容可知,当asio::io_context::run()函数没有更多"任务"需要处理时会自动结束。
    // 由于在计数器达到5时未启动新的异步定时器等待操作,io_context将因任务耗尽而停止运行。
    if (*count < 5) {
        spdlog::info("count = {}", *count);
        ++(*count);

        // 接下来我们将计时器的到期时间从前一个到期时间开始向后移动一秒。
        // 通过相对于旧时间计算新的到期时间,可以确保计时器不会因处理程序的延迟而偏离整秒标记。
        t->expires_at(t->expiry() + asio::chrono::seconds(1));

        // 然后我们开始对计时器进行新的异步等待。
        // 如你所见,std::bind函数用于将额外参数与完成处理程序关联起来。
        // steady_timer::async_wait()函数需要一个签名为void(const std::error_code&)的处理函数(或函数对象)。
        // 通过绑定额外参数,print函数被转换为一个正确匹配该签名的函数对象。
        //
        //在这个例子中,std::bind的asio::placeholders::error参数是传递给处理程序的错误对象的命名占位符。
        // 当启动异步操作时,如果使用std::bind,必须仅指定与处理程序参数列表匹配的参数。
        // 在教程Timer.4中你会看到,如果完成处理程序不需要该参数,可以省略这个占位符。
        t->async_wait(std::bind(print, asio::placeholders::error, t, count));
    }
}

int main(int argc, char* argv[])
{
    spdlog::info("Start wait 5 seconds");

    // 统计异步操作次数
    int count {};
    asio::io_context io;
    asio::steady_timer t(io, std::chrono::seconds(1));

    // 当从主函数调用steady_timer::async_wait()时,我们绑定了print函数所需的额外参数。
    t.async_wait(std::bind(print, asio::placeholders::error, &t, &count));

    io.run();

    // 最后,为了证明计数变量在打印处理函数中被使用,我们将打印出它的新值。
    spdlog::info("Final count = {}", count);

    return 0;
}