第13章 网络 Page729~733 链式任务反应

链式任务反应

当io_service对象身上没有任务的时候,当前正在运行的run()过程就结束了。这时再往它身上添加任务,程序收不到任务完成事件。

如果本次任务完成后,run()函数退出前再添加一项或更多任务,这就叫链式任务

在asio的异步世界里,链式任务是最常用的任务产生模式

新建控制台项目asio_countdown

思路:和Peng结构一样,由于存在状态传递以及会重复调用,所以考虑使用“函数对象”作为定时的回调动作(否则就可能要用到全局数据或者使用binder以绑定状态数据了)。这次的结构叫“DownCounter(倒计数器)”,其中最关键的括号操作符重载函数为:

struct DownCounter
{
    void operator()(boost::system::error_code const& err)
    {
        if(err)
            return;

        if(--_count != 0)
        {
            //产生下一秒定时:
            timer.expires_from_now(std::chrono::seconds(1));
            timer.async_wait(*this);
        }
    }
};

看到变量名字前面的下划线,应该能猜到_count是DownCounter结构的一个成员数据。另外也应该能想到"_count"会从10变到0,只要还不是0,就会在每次调用asio定时器timer的async_wait()方法开始等下一秒,背后是向当前io_service对象添加一个新的任务。

012行添加了新的定时任务,但这个定时任务的到点事件一定会发生吗?这就得看对应的io_service对象是否处于“运转”状态。012行所处的位置就是一个事件函数,这个事件函数当前正在被执行,所以事件所属的io_service对象肯定在运转中。这正是asio编程的一个惯用法:在当前事件处理中,添加后续新的任务(11行,添加新的定时任务)(也可以直接添加新事件,将在更后一点讲解),从而保障新事件有机会被触发。这个过程称为“链式反应”。链式反应的结果是,可怜的io_service对象将无休无止地处于"run(运转)"状态。

继续看012行处的代码,此处的timer对象从何而来?之前的asio_timer例子是在main()函数中定义定时器对象。如果本例中也这样做,就需要在事件接口(第三行operator()函数)添加一个入参用于传递timer对象造成入参和async_wait(Action)所需要的回调原型不一致,因此需要使用“bing(绑定)”技术。尽管使用asio编程早晚得用到bind()函数,但现在我们尽量不让事情变复杂。方法是将timer对象定义成DownCounter结构的成员数据,就像“_count”一样。如此我们得到一个直观而且简洁的封装。

相对完整的DownCounter结构定义如下:

struct DownCounter
{
    DownCounter(boost::asio::io_service& ios, int count)
        : _timer(ios), _count(count) 
    {
    }

    void operator() (boost::system::error_code const& err)
    {
        if(err)
            return;

        cout << _count << " ";

        if(-- _count != 0)
        {
            //产生下一秒定时
            _timer.expires_from_now(std::chrono::seconds(1));
            _timer.async_wait(std::ref(*this));
        }
    }

private:
    boost::asio::system_timer _timer;//变成一个成员
    int _count;
};

先看DownCounter的构造函数。"_timer"是一个"I/O"对象,它的构造需要一个io_service对象。接着看“产生下一秒定时”的地方,这次我们为“*this”加上了std::ref,以确保传递当前对象的引用而非复制品,因为DownCounter类含有boost::asio::system_timer类型的成员,它不支持简单的复制

危险游戏:在异步过程间传递对象的引用

既然改成传递“引用”,我们心里要拉紧一根弦:确保DownCounter对象的生命周期足够长。在异步过程中传递引用,非常容易引发“时空”混乱。

在看一遍DownCounter的定义,然后心中想象这样一个过程:第1秒过去,于是当前DownCounter对象的operator()方法被调用,调用过程里执行“_timer.async_wait()”,传入的还是当前DownCounter对象;再过1秒,该DownCounter对象的operator()方法又被调用,于是再来一次异步等待……其间“_count”成员每次减1,直到变成0,停下这个周而复始的过程

你想到了什么?想到了“递归”?这确实是一个逻辑上的递归过程,但它确实不是程序中函数递归调用的过程;因为对operator()方法的每次调用都是干干净净地退出。如果你不信,将来程序运行时,可以通过调试查看函数调用栈

谁在这个程序中负责调用“_timer”对象的第一次“async_wait()(异步等待)”呢?我想到了“第一推动力”。于是为DownCounter类添加一个公开的Start()方法,以便上帝调用它。同时由于出现重复代码,所以添加了一个私有的StartNextSeconds()成员函数。至此,

完整的DownCounter类设计出来了

struct DownCounter
{
    DownCounter(boost::asio::io_service& ios, int count)
        : _timer(ios), _count(count) 
    {
    }

    void Start()
    {
        StartNextSeconds();
    }
 
    void operator() (boost::system::error_code const& err)
    {
        if(err)
            return;

        cout << _count << " ";

        if(-- _count != 0)
        {
            StartNextSeconds();
        }
        else
        {
            cout << "发射!" << endl;
        }
    }

private:
    void StartNextSeconds() 
    {
        _timer.expires_from_now(std::chrono::seconds(1));
        _timer.async_wait(std::ref(*this));
    }

    boost::asio::system_timer _timer;
    int _count;
};

完整代码:

最关键的代码是括号操作符重载函数24~39行

运行效果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值