程序代码:
11行,声明一个ios对象
13行,使用ios对象作为参数声明一个定时器,此时,定时器和ios完成了关联,后面定时器如果有任务的话,就可以将任务交给ios
16行,为定时器设置一个定时任务,13行,定时器已经和ios已经了关联,定时器就可以将定时器交给ios,定时工作由操作系统完成,所以这一行实际上,是将任务添加到io_service身上,因为这个定时任务,是由操作系统完成的。将任务添加到io_service身上,并不需要ios"转"起来。
23~33行,使用定时器的async_wait()方法,为定时器设置一个回调动作(函数指针,函数对象,Lambda表达式,std::function对象都可以),当定时器的定时任务由ios完成以后(操作系统完成),操作系统则"吐"出一个事件给ios,告诉ios,定时任务完成了,你可以发起回调动作了, 如果ios空闲,并且ios的run()方法正在被调用,则ios处理此事件,发起回调动作,就是本例中Lamabda表达式。
37行,启动io_service服务,定时器定时任务由ios交给操作系统完成后,操作系统回馈给ios一个事件,通知ios任务已经完成,ios接收到此事件,便会发起回调动作。启动定时器的回调活动(async_wait()函数的参数)。若被注释掉,则定时任务完成后,不会启动回调活动。
综合起来,过程是这样的,13行,完成定时器与ios的关联,16行,为定时器设置一个任务,这个任务会被ios交给操作系统完成(这一步不需要ios"转"起来),操作系统完成任务完成后,回馈一个事件给ios, 通知ios,任务已经完成了,可以发起回调了,37行,如果ios的run()方法已经调用了,则,ios将发起回调动作,就是执行23行(设置一个回调动作)的Lambda表达式。
ios_service
ios_service(输入输出_服务),在asio新版中也叫io_context(输入输出_上下文)。更好的名称应是"io_engine(输入输出_引擎)";run一词,不能简单地认为是有个小人在跑,而是要想到"运转"这个词。
本文中的程序第16行,就是往ios_service身上丢个定时任务
当我们调用io_service的run()方法,引擎的齿轮就转了起来,然后需要说三遍的重要事情来了:io_service对象的run()方法并非永久地转下去,它只是转到没有任务就立即停转(函数返回)
当io_service对象"转"起来(即run()方法正在执行),它就会去处理身上的事件。当全部任务的全部事件都处理完成,该io_service对象就不再转动。后续要是又有新任务往它身上加,除非再次调用run(),否则任务都不会被执行,最终也许任务堆积成山。
任务来自何处?任务谁来完成?任务去往何处?
一、任务来自何处?
任务来自应用程序,比如我们写的代码:
timer_1.async_wait(...);
timer_1在执行async_wait()方法时,创建了一个异步任务,并且将该任务交给io_service对象,即本例中的ios变量。timer_1什么时候认识的ios对象的呢?请看它的构造过程:
boost::asio::system_timer timer_1(ios);
time_1是一个对象,它拥有一件有赖外部环境推动完成的任务。这样的对象在asio中称为"I/O对象"。比如说"定时器",之前在"异步、异步、异步"小节中演示了两种实现方法:一是当前应用程序的当前线程直接堵塞"睡"上一段时间,二是当前应用程序新开一个线程,让那个线程"睡上"上一段时间。尽管后者的表现效果是"异步",但与前者没有本质区别,因为在等待事情完成的期间,都百分百地耗费当前应用程序的一个线程。asio提供的timer,则将定时的工作交给操作系统或特定支撑框架,因为底层往往可以提供低成本的定时器实现。
小提示:定时器和"I/O"有关系吗?
可以这样理解,我们交个操作系统一个时长,经过该时长后,操作系统回馈一个信号以示"定时到啦",这一进一出不就是"I/O"?
二、任务谁来完成?
答:任务(本例中为定时工作)被io_service交给操作系统完成。
三、任务去往何处?
答:任务完成后就结束使命了,但为了让上层应用程序知道任务完成了,io_service对象会触发一个事件。事件就是创建任务时指定的回调动作,比如本例中timer_1.async_wait(...)调用时入参所指定的lambda表达式。因此可以理解为任务走了,事件来了。当然,前提是有线程在调用该io_service对象的run()方法。
现在,关于io_service的图示如图13-18所示
任务交给操作系统之后,线程就空闲了,可以去处理别的任务。而操作系统完成任务后,将"吐"出事件给某个io_service对象,此时如果有一个线程正好在运行该io_service对象的run()方法,并且它空闲(没有在处理之前的事件),它就有机会接收并处理该事件。如果这样的线程有多个,asio保障只会有一个线程接收并处理同一个事件。如果没有任何这样的线程,新事件就会无人受理。
程序的调试
若我们在第25行打一个断点,则会发现,调试启动以后,程序并不会立即停在第25行,而是在定时器任务完成之后,触发回调活动,程序才会停在第25行,这充分说明,回调活动是异步执行的