1. 前言
大家比较好奇,我们讲了那么多的协称和事件循环知识,为啥一直没有提到事件循环如何调度协称的呢?不好意思哈,我想把大家的好奇心留在这一篇文章中,接下来我希望你们拿好板凳,带上瓜子和水,用眼睛往下瞅。
我们接下来了解两个概念,在说说协称如何被调度
2. Future对象
在asyncio
中,如何才能得到异步调用的结果呢?先设计一个对象,异步调用执行完的时候,就把结果放在它里面,这种对象称之为未来对象。未来对象有一个result
方法,可以获取未来对象的内部结果。还有个set_result
方法,是用于设置result
的。set_result
设置的是什么,调用result
得到的就是什么。Future
对象可以看作下面的Task
对象的容器。
3. Task对象
一个协程就是一个原生可以挂起的函数,Task
则是对象协程的进一步封装,里面可以包含协程在执行时的各种状态,关于Task
和Future
两者之前的关系我们后面会说。
4. Task和Future与协程的关系
Task
是Future
的派生类,它有一个核心step
方法,这个方法和协称调度有关,但是这个方法只有Task
独有,Future
是没有的,但是Future
有set_result
方法,这个方法可以被Future
和Task
共同调用。同时Task
是Future
和协称之间的桥梁,因为Task
执行结束的时候会调用Future
的set_result
方法,这样Future
通过result
方法就会知道协称运行的结果,所以说Future
想要知道协称的运行结果,那么必须将协称绑定Task
,这样Task
才能把结果设置到Future
中。
5. 协称如何被事件循环调度
对于协程来说,是没有办法直接放到事件循环里面运行的,需要Task
对象(任务)。而我们之前直接将协程扔进loop中是因为asyncio
内部会有检测机制,如果是协程的话,会自动将协程包装成一个Task
对象。例如:
import asyncio async def coroutine(): print("hello world") if __name__ == "__main__": loop = asyncio.get_event_loop() # 如何创建一个任务呢? task = loop.create_task(coroutine()) loop.run_until_complete(task) 运行结果: """ hello world """
事件循环怎么调度协称的呢?
loop通过
create_task(coroutine())
创建task,入参是协称,task在初始化的时候会调用:loop.call_soon(self._step)
,然后call_soon
会在loop的_ready
回调队列中添加一个Handle
实例,最后为task对象添加一个done callback
来停止loop。task初始化完成,我们运行
loop.run_until_complete(task)
最终会调用loop.run_forever()
。
loop.run_forever()
本质是一个while循环,只要loop没有被标记为停止,就会反复调用self._run_once()
,在_run_once()
中,loop会将_ready
回调队列中所有的Handle
实例依次取出并执行,这里就执行到了前面task实例的_step()
;
_step
方法会执行两个重要操作,第一个就是启动协称的运行,通过调用协称的send(None)
方法启动。第二个就是协称运行完毕会抛出StopIteration
异常,而这个异常里面的value
就是协称返回的值,把这个value
通过Future
的set_result
方法设置进去,这样我们的Task
或者Future
就会通过Result
获取返回值,即协称的返回值。我们通常在代码中等待协称返回都是如下这种方式:
result = await future //相当于调用Future.Result()方法 result = await task //同上
这样我们就可以拿到协称的返回值,而事件循环会继续在_ready
队列中查找有没有其他的待运行Handler
实例。
参考:https://segmentfault.com/q/1010000016451903
6. 小结
总结一下核心重点:Task
是Future
和协称之间的桥梁,而协称的调度离不开事件循环,事件循环又通过Task
生成Handler
实例,最终被事件循环调度,所以说asyncio
复杂背后的设计逻辑是环环相扣的,只有把相关概念和原理吃透,你才能彻底掌握他们。