抽丝剥茧分析asyncio事件调度的核心原理
先来看一下一个简单的例子
例1:
async def foo():
print('enter foo ...')
await bar()
print('exit foo ...')
async def bar():
print('enter bar ...')
print('exit bar ...')
f = foo()
try:
f.send(None)
except StopIteration as e:
print(e.value)
例2:
async def foo():
print('enter foo ...')
try:
bar().send(None)
except StopIteration as e:
pass
print('exit foo ...')
async def bar():
print('enter bar ...')
print('exit bar ...')
f = foo()
try:
f.send(None)
except StopIteration as e:
print(e.value)
也就是说 await bar()
等价于这个
try:
bar().send(None)
except StopIteration as e:
pass
更进一步来讲,await 协程的嵌套就跟函数调用一样,没什么两样。
def foo():
print('enter foo ...')
bar()
print('exit foo ...')
def bar():
print('enter bar ...')
print('exit bar ...')
foo()
理解了跟函数调用一样就可以看看成是这样:
执行f.send(None)时其实就是执行
print('enter foo ...')
print('enter bar ...')
print('exit bar ...')
print('exit foo ...')
例3:
class Future:
def __iter__(self):
print('enter Future ...')
yield self
print('foo 恢复执行')
print('exit Future ...')
__await__ = __iter__
async def foo():
print('enter foo ...')
await bar()
print('exit foo ...')
async def bar():
future = Future()
print('enter bar ...')
await future
print('exit bar ...')
f = foo()
try:
f.send(None)
print('foo 挂起在yield处 ')
print('--'*10)
f.send(None)
except StopIteration as e:
print(e.value)
执行结果:
enter foo ...
enter bar ...
enter Future ...
foo 挂起在yield处
--------------------
foo 恢复执行
exit Future ...
exit bar ...
exit foo ...
None
Future是一个Awaitable对象,实现了__await__方法,await future 实际上是会进入到future.__await__方法中也就是future.__iter__方法中的逻辑,执行到 yield self 处foo协程才真正被挂起,返回future对象本身,f.send(None)才真正的执行完毕,
第一次调用f.send(None),执行:
print('enter foo ...')
print('enter bar ...')
print('enter Future ...')
被挂起
第二次调用f.send(None),执行:
print('exit Future ...')
print('exit bar ...')
print('exit foo ...')
也就是说这样一个foo协程完整的调用过程就是如下过程:
- foo print('enter foo ...')
- bar print('enter bar ...')
- future print('enter Future ...') # 以上是第一次f.send(None)执行的逻辑,命名为part1
- future yield self ---------------------------------------------------------------
- future print('exit Future ...') # 以下是第二次f.send(None)执行的逻辑,命名为part2
- bar print('exit bar ...')
- foo print('exit foo ...')
加入我们把这两次f.send(None)调用的逻辑分别命名成part1和part2,那也就是说,通过future这个对象,准确的说是yield关键字,真正的把foo协程要执行的完整逻辑分成了两部分part1和patr2。并且foo的协程状态会被挂起在yield处,这样就要调用两次f.send(None)才能,执行完foo协程,而不是在例2中,直接只调用一次f.send(None)就执行完了foo协程。这就是Future对象的作用。
重点:没有 await future 的协程是没有灵魂的协程,并不会被真正的挂起,只需一次 send(None) 调用即可执行完毕,只有有 await future
的协程才是真正可以被挂起的协程,需要执行两次 send(None) 才能执行完该协程的完整逻辑。
这里小结一下Future的作用
yield 起到了挂起协程的作用。
通过 yield 把 foo 协程的执行逻辑真正的分成了 part1 和 part2 两部分。
例4:
class Future:
def __iter__(self):
print('enter Future ...')
print('foo 挂起在yield处 ')
yield self
print('foo 恢复执行')
print('exit Future ...')
return 'future'
__await__ = __iter__
class Task:
def __init__(self, cor):
self.cor = cor
def _step(self):
cor = self.cor
try:
result = cor.send(None)
except Exception as e:
pass
async def foo():
print('enter foo ...')
await bar()
print('exit foo ...')
async def bar():
future = Future()
print('enter bar ...')
await future
print('exit bar ...')
f = foo()
task = Task(f)
task._step()
print('--' * 10)
task._step()
执行结果:
enter foo ...
enter bar ...
enter Future ...
foo 挂起在yield处
--------------------
foo 恢复执行
exit Future ...
exit bar ...
exit foo ...
这个例4与例3不同在于,现在有一个Task类,我们把f.send(None)d的操作,封装在了 Task 的 _step 方法中,调用 task._step() 等于是执行 part1 中的逻辑,再次调用
task._step() 等于是执行part2中的逻辑。现在不想手动的 两次调用task._step() 方法,我们写一个简单的Loop类来帮忙完成对task._step的多次调用,请看下面这个例子。
例5:
class Future:
def __iter__(self):
print('enter Future ...')
print('foo 挂起在yield处 ')
yield self
print('foo 恢复执行')
print('exit Future ...')
return 'future'
__await__ = __iter__
class Task:
def __init__(self, cor, *, loop=None):
self.cor = cor
self._loop = loop
def _step(self):
cor = self.cor
try:
result = cor.send(None)
except StopIteration as e:
self._loop.close()
except Exception as e:
pass
class Loop:
def __init__(self):
self._stop = False
def create_task(self, cor):
task = Task(cor, loop = self)
return task
def run_until_complete(self, task):
while not self._stop:
task._step()
def close(self):
self._stop = True
async def foo():
print('enter foo ...')
await bar()
print('exit foo ...')
async def bar():
future = Future()
print('enter bar ...')
await future
print('exit bar ...')
if __name__ == '__main__':
f = foo()
loop = Loop()
task = loop.create_task(f)
loop.run_until_complete(task)
执行结果:
enter foo ...
enter bar ...
enter Future ...
foo 挂起在yield处
foo 恢复执行
exit Future ...
exit bar ...
exit foo ...
例5中我们实现了一个简单 Loop 类,在while循环中调用task._step方法。
例6:
class Future:
def __init__(self, *, loop=None):
self._result = None
self._callbacks = []
def set_result(self, result):
self._result = result
callbacks = self._callbacks[:]
self._callbacks = []
for callback in callbacks:
loop._ready.append(callback)
def add_callback(self, callback):
self._callbacks.append(callback)
def __iter__(self):
print('enter Future ...')
print('foo 挂起在yield处 ')
yield self
print('foo 恢复执行')
print('exit Future ...')
return 'future'
__await__ = __iter__
class Task:
def __init__(self, cor, *, loop=None):
self.cor = cor
self._loop = loop
def _step(self):
cor = self.cor
try:
result = cor.send(None)
# 1. cor 协程执行完毕时,会抛出StopIteration,说明cor执行完毕了,这是关闭loop
except StopIteration as e:
self._loop.close()
# 2. 有异常时
except Exception as e:
"""处理异常逻辑"""
# 3. result为Future对象时
else:
if isinstance(result, Future):
result.add_callback(self._wakeup)
# 立即调用,让下一loop轮循环中立马执行self._wakeup
result.set_result(None)
def _wakeup(self):
self._step()
class Loop:
def __init__(self):
self._stop = False
self._ready = []
def create_task(self, cor):
task = Task(cor, loop = self)
self._ready.append(task._step)
return task
def run_until_complete(self, task):
assert isinstance(task, Task)
while not self._stop:
n = len(self._ready)
for i in range(n):
step = self._ready.pop()
step()
def close(self):
self._stop = True
async def foo():
print('enter foo ...')
await bar()
print('exit foo ...')
async def bar():
future = Future(loop=loop)
print('enter bar ...')
await future
print('exit bar ...')
if __name__ == '__main__':
f = foo()
loop = Loop()
task = loop.create_task(f)
loop.run_until_complete(task)
执行结果:
enter foo ...
enter bar ...
enter Future ...
foo 挂起在yield处
foo 恢复执行
exit Future ...
exit bar ...
exit foo ...
在例6中,我们构建了3个稍微复杂点类,Loop类,Task, Future类,这3个类在整个协程执行流程的调度过程中有很强的相互作用关系。
Future
挂起协程的执行流程,把协程的逻辑分为part1和part2两部分。
Task
把协程的part1和part2逻辑封装到task._step和task._wakeup方法中,在不同的时机分别把它们注册到loop对象中,task._step是创建task实例的时候就注册到了loop中,task._wakeup则是在task._setp执行完挂在yield future处,由于有await future语句的存在,必然是返回一个future对象,判断确实是一个future对象,就把task._wakeup注册到future中,future.set_result()则会在合适的时机被调用,一旦它被调用,就会把future中注册的task._wakeup注册到loop中,然后就会在loop循环中调用task._wakeup,协程的part2的逻辑才得以执行,最后抛出StopIteration异常。
Loop
在一个死循环中执行注册到loop中的task._step和task._wakeup方法,完成对协程完整逻辑的执行。
虽然我们自己构建的这三个类的实现很简单,但是这体现asyncio实现事件循环的核心原理,我们实现loop中并没有模拟耗时等待以及对真正IO事件的监听,对应于asyncio来说,它也是构建了Future, Task, Loop这3个类,只是功能要比我们自己构建的要复杂得多,loop对象的while中通过select(timeout)函数的调用实现模拟耗时操作和实现了对网络IO事件的监听,这样我们只要在写了一个执行一个IO操作时,都会有一个future对象 await future,通过future来挂起当前的协程,比如想进行一个socket连接,协程的伪代码如下:
future = Future
# 非阻塞调用,需要try...except...
socket.connect((host, port))
# 注册一个回调函数到write_callbackselect中,只要socket发生可写事件,就执行回调
add_writer(write_callback, future)
await future
...
当我们在调用socket.connect((host, port)),因为是非阻塞socket,会立马返回,然后把这个write_callback, future注册成select的可写事件的回调函数,这个回调函数什么时候被执行呢,就是在loop循环的select(timeout)返回了可写事件时才会触发,回调函数中会调用future.set_result(),也就是说future.set_result的触发时机是在socket连接成功时,select(timeout)返回了可写事件时,future.set_result的作用就是把协程的part2部分注册到loop,然后在下一轮的循环中立即调用,使得协程的await future下面的语句得以继续执行。
由于我这里没有贴asyncio的loop,task,future对象的源码,所以这个例子看起来会很抽象,在上一篇asyncio中贴了这几个类的源码,想详细了解的可以查看我的上一篇文章《asyncio系列之简单协程的基本执行流程分析》。小伙伴们也可以对照着asyncio的源码来debug,这样再来理解这里说的这个例子就比较容易了。
下一篇将介绍asyncio.sleep()的实现机制。
抽丝剥茧分析asyncio事件调度的核心原理的更多相关文章
- Java Reference核心原理分析
本文转载自Java Reference核心原理分析 导语 带着问题,看源码针对性会更强一点.印象会更深刻.并且效果也会更好.所以我先卖个关子,提两个问题(没准下次跳槽时就被问到). 我们可以用Byte ...
- Redis核心原理与实践--Redis启动过程源码分析
Redis服务器负责接收处理用户请求,为用户提供服务. Redis服务器的启动命令格式如下: redis-server [ configfile ] [ options ] configfile参数指 ...
- 《大型网站技术架构:核心原理与案例分析》【PDF】下载
<大型网站技术架构:核心原理与案例分析>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062557 内容简介 本书通过梳理大型网站 ...
- Redis核心原理与实践--事务实践与源码分析
Redis支持事务机制,但Redis的事务机制与传统关系型数据库的事务机制并不相同. Redis事务的本质是一组命令的集合(命令队列).事务可以一次执行多个命令,并提供以下保证: (1)事务中的所有命 ...
- Spring核心原理分析之MVC九大组件(1)
本文节选自<Spring 5核心原理> 1 什么是Spring MVC Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 S ...
- 剖析SSH核心原理(一)
在我前面的文章中,也试图总结过SSH,见 http://blog.csdn.net/shan9liang/article/details/8803989 ,随着知识的积累,总感觉以前说得比较笼统, ...
- Redis核心原理
Redis系统介绍: Redis的基础介绍与安装使用步骤:https://www.jianshu.com/p/2a23257af57b Redis的基础数据结构与使用:https://www.jian ...
- c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)
c#封装DBHelper类 public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...
- 深入解析Koa之核心原理
这篇文章主要介绍了玩转Koa之核心原理分析,本文从封装创建应用程序函数.扩展res和req.中间件实现原理.异常处理的等这几个方面来介绍,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参 ...
随机推荐
- STM32 模拟I2C (STM32F051)
/** ****************************************************************************** * @file i2c simu. ...
- JS正则--非负整数或小数[小数最多精确到小数点后两位]
function ValidPrice(obj) { s = obj.value; //var reg = /^[0-9]*\.[0-9]{0,2}$/; var reg = /^[0-9]+([.] ...
- Converter
public class ImgPathConvert : IValueConverter { public object Convert(object value, Type targetType, ...
- jquery动态操作元素
<!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...
- Styling a ListView with a Horizontal ItemsPanel and a Header
原文http://eblog.cloudplush.com/2012/05/23/styling-a-listview-with-a-horizontal-itemspanel-and-a-heade ...
- Win8 Metro(C#)数字图像处理--2.41彩色图像密度分割算法
原文:Win8 Metro(C#)数字图像处理--2.41彩色图像密度分割算法 [函数名称] 彩色图像密度分割函数 DensitySegmentProcess(WriteableB ...
- 微信小程序把玩(二十一)switch组件
原文:微信小程序把玩(二十一)switch组件 switch开关组件使用主要属性: wxml <!--switch类型开关--> <view>switch类型开关</vi ...
- 无法解决 equal to 操作中 "SQL_Latin1_General_CP1_CI_AS" 和 "Chinese_PRC_CI_AS" 之间的排序规则冲突。
无法解决 equal to 操作中 "SQL_Latin1_General_CP1_CI_AS" 和 "Chinese_PRC_CI_AS" 之间的排序规则冲突 ...
- Qt:正确判断文件、文件夹是否存在的方法
一直对Qt的isFile.isDir.exists这几个方法感到混乱,不知道到底用哪个,网上搜了下资料,也是用这几个方法但是都没有对其深究,经过测试发现会存在问题,先看看下面的测试代码 { QFile ...
- CSS3 Generator提供了13个CSS3较为常用的属性代码生成工具,而且可以通过这款工具除了在线生成效果代码之外,还可以实时看到你修改的效果,以及浏览器的兼容性。
CSS3 Generator提供了13个CSS3较为常用的属性代码生成工具,而且可以通过这款工具除了在线生成效果代码之外,还可以实时看到你修改的效果,以及浏览器的兼容性. CSS3 Generator ...