先来看一下一个简单的例子

例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的作用

  1. yield 起到了挂起协程的作用。

  2. 通过 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事件调度的核心原理的更多相关文章

  1. Java Reference核心原理分析

    本文转载自Java Reference核心原理分析 导语 带着问题,看源码针对性会更强一点.印象会更深刻.并且效果也会更好.所以我先卖个关子,提两个问题(没准下次跳槽时就被问到). 我们可以用Byte ...

  2. Redis核心原理与实践--Redis启动过程源码分析

    Redis服务器负责接收处理用户请求,为用户提供服务. Redis服务器的启动命令格式如下: redis-server [ configfile ] [ options ] configfile参数指 ...

  3. 《大型网站技术架构:核心原理与案例分析》【PDF】下载

    <大型网站技术架构:核心原理与案例分析>[PDF]下载链接: https://u253469.pipipan.com/fs/253469-230062557 内容简介 本书通过梳理大型网站 ...

  4. Redis核心原理与实践--事务实践与源码分析

    Redis支持事务机制,但Redis的事务机制与传统关系型数据库的事务机制并不相同. Redis事务的本质是一组命令的集合(命令队列).事务可以一次执行多个命令,并提供以下保证: (1)事务中的所有命 ...

  5. Spring核心原理分析之MVC九大组件(1)

    本文节选自<Spring 5核心原理> 1 什么是Spring MVC Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 S ...

  6. 剖析SSH核心原理(一)

      在我前面的文章中,也试图总结过SSH,见 http://blog.csdn.net/shan9liang/article/details/8803989 ,随着知识的积累,总感觉以前说得比较笼统, ...

  7. Redis核心原理

    Redis系统介绍: Redis的基础介绍与安装使用步骤:https://www.jianshu.com/p/2a23257af57b Redis的基础数据结构与使用:https://www.jian ...

  8. c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)

    c#封装DBHelper类   public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...

  9. 深入解析Koa之核心原理

    这篇文章主要介绍了玩转Koa之核心原理分析,本文从封装创建应用程序函数.扩展res和req.中间件实现原理.异常处理的等这几个方面来介绍,写的十分的全面细致,具有一定的参考价值,对此有需要的朋友可以参 ...

随机推荐

  1. WPF - 善用路由事件

    原文:WPF - 善用路由事件 在原来的公司中,编写自定义控件是常常遇到的任务.但这些控件常常拥有一个不怎么好的特点:无论是内部还是外部都没有使用路由事件.那我们应该怎样宰自定义控件开发中使用路由事件 ...

  2. 关于hibernate一对一配置

    实体类关系 一对一 一对多 多对一 多对多 Hibernate提供了两种映射一对一关联关系的方式:按照外键映射和按照主键映射.下面以员工账号和员工档案表为例,介绍这两种映射方式,并使用这两种映射方式分 ...

  3. 【转载】MySQL和Keepalived高可用双主复制

    服务器主机IP和虚拟浮动IP配置 RealServer A 192.168.75.133 RealServer B 192.168.75.134 VIP A 192.168.75.110 VIP B ...

  4. WPF中任意Object的XAML代码格式化输出

    原文:WPF中任意Object的XAML代码格式化输出 有时候,我们需要将WPF中的控件自身的XAML代码输出成文本,那么,我们可以使用System.Windows.Markup.XamlWriter ...

  5. 【WPF】UI虚拟化之------自定义VirtualizingWrapPanel

    原文:[WPF]UI虚拟化之------自定义VirtualizingWrapPanel 前言 前几天QA报了一个关于OOM的bug,在排查的过程中发现,ListBox控件中被塞入了过多的Item,而 ...

  6. Linux命令扫盲 之 sar

    今天在读<大规模Web服务开发技术>一书的时候,书中提到了sar这个命令,感觉很有用,有必要整理学习一下.(对于一位Linux初学者,不能放过任何一个学习机会 :P) 打开自己的CentO ...

  7. grep专题

    grep -R --include="*.cpp" key dir[指定文件的扩展名] 上述命令的含义: 在dir目录下递归查找所有.cpp文件中的关键字key grep -r m ...

  8. windows和linux双系统,重新分区后修复grub

    我电脑里装的是 windows 7 profressional 和 fedora 12 ,本来给 C 盘分了 50G ,原本以为够了,结果50G 瞬间用完,于是乎开始重新分区. PQ 是不能用了,上网 ...

  9. layer 1.9.2 发布,国产 Web 弹层不懈的前行者

    快速使用Romanysoft LAB的技术实现 HTML 开发Mac OS App,并销售到苹果应用商店中.   <HTML开发Mac OS App 视频教程> 土豆网同步更新:http: ...

  10. InfoPath分别定义New/Edit 表单

    InforPath自定义表单时,默认是New/Edit是相同的,有时不能满足特殊情况,例如,某些字段在新建时不需填,编辑才改.或者编辑时不能编辑特定字段.这时最方便的方法是分别定义表单,按不同情况使用 ...