链式任务反应
当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行