Tornado源码分析系列之一: 化异步为'同步'的Future和gen.coroutine
转自:http://blog.nathon.wang/2015/06/24/tornado-source-insight-01-gen/
用Tornado也有一段时间,Tornado的文档还是比较匮乏的,但是幸好其代码短小精悍,很有可读性,遇到问题时总是习惯深入到其源码中。
这对于提升自己的Python水平和对于网络及HTTP的协议的理解也很有帮助。本文是Tornado源码系列的第一篇文章,网上关于Tornado源码分
析的文章也不少,大多是从Event loop入手,分析Event loop的工作原理,以及在其上如何构建TCPServer和HTTPServer。所以我就不想拾前
人的牙慧再去写一遍,当然这些内容我后续会涉及到,但是做为开篇第一章,我想从更加独特的角度来分析Tornado,这里就说说Tornado的gen
和concurrent两个模块, 这个话题网上似乎还不多,呵呵。
设计从需求出发,要考证一段的代码为什么写成这样而不是那样, 首先要看代码解决了什么需求。 看下代码中的例子先:
1 |
class AsyncHandler(RequestHandler): |
经过gen.coroutine修饰之后上面的这段代码可以改为
1 |
class GenAsyncHandler(RequestHandler): |
初识这段代码觉得好神奇,其实gen.coroutine只不过是将一个基于callback的典型的异步调用适配成基于yield的伪同步,说是伪同步是因为代码流程上类
似同步,但是实际却是异步的。这样做有几个好处:
1。控制流跟同步类似,我们知道callback里去做控制流还是比较恶心的,就算nodejs里的async这样的模块,但是分支多起来也非常不好写。(爽)
2。可以共享变量,没有了callback,所有的本地变量在同一个作用域中。 (爽爽)
3。可以并行执行,yield可以抛出list或dict,并行执行其中的异步流程。(爽爽爽。。。此处省略一万个爽)
神奇的gen.coroutine装饰器是怎么做到这一切的?让我首先买个关子,不是进入到gen里面分析coroutine和Runner这两核心的方法(类),而是首先分析一些这
些方法(类)中用到的一些技术, 然后再回到coroutine装饰器和Runner类中。
首先要理解的是generator是如何通过yield与外界进行通信的。
1 |
def test(): |
步骤1启动了generator,步骤2向generator内部发送数据,并通过yield向generator外部抛出结果10, 最后的执行结果是
1 |
step 1....... |
然后让我再说说Future,Future是对异步调用结果的封装。一个callback型的异步调用的执行结果不仅包括调用的返回,还包括调用获得返回之后需要执行的回调,所以才需要将
异步调用的结果封装一下,作为一个异步调用执行结果的占位符。Future类基本可以这么写
1 |
class Future(object): |
当然这只是个简约版的,详细可以参看concurrent.Future。
最后再来说说另一个重要的函数Task, 这个函数的主要作用是将一个callback型的异步调用适配成一个返回Future的异步调用,而这个作为异步调用结果的Future会在原来的那个callback被时解析出来
1 |
def Task(func, *args, **kwargs): |
这里忽略了一些与本文无关的部分。可以看到Task里面构造了一个callback,_argument_adapter是将callback的参数进行适配,将不定参数适配成一个参数也就是result, 最后通过
future.set_result(result)将result赋值给future,这样future就被解析出来。 那么问题来了,AsyncHTTPClient并没有经过Task的适配,而是直接返回一个Future。这个Future是在
什么时候解析的呢?进httpclient.py来看下AsyncHTTPClient是如何解析Future的,这是httpclient.py中的fetch函数,也就是我们实际发起http请求的那个函数
1 |
def fetch(self, request, callback=None, raise_error=True, **kwargs): |
fetch中定义一个代表fetch异步调用执行结果的future,如果调用时传入了callback,并不是直接将callback传给fetch_impl,而是首先给future设置一个名为handle_future解析完成后的回调,这个handle_future
中通过add_callback把实际传进来的callback加入到IOLoop中让IOLoop规划其调用。而传入到fetch_impl中的callback 则换成被了handle_response这个函数,
fetch_impl最后会在当收到response的时候调用handle_response回调(这个有兴趣可以看下,如果以后有写httpserver相关的分析可能会再分析), handle_response会解析出代表执行结果的future。对没有设置callback的调用,future解析结束整个流程也就结束了。而对于设置了callback的调用,future完成之后会调用handle_future 。
画个简图来描述一下调用过程
fetch->fetch_impl->HTTP请求直到有response或出错,如果有response回调handle_response->future.set_result(response)(future有值了)->如果fetch带了callback则handle_future->ioloop中调用callback
至此可以看到AsyncHTTPClient是如何把一个callback型的异步调用转换成一个返回future的异步调用,而这个future会在handle_response调用时被解析得到返回的response。
好了,差不多该深入gen.coroutine这个装饰器以及其最终实现Runner类。其实看完上面的内容gen.coroutine和Runner的作用也呼之欲出,其主要功能就是拿到yield出的异步调用返回的future,看这个
future是否已经完成,如果完成就把结果再send到generator中,如果没有完成就要为future设置一个完成时回调,这个回调的主要作用就是启动Runner(也就是调用run方法)。至于future啥时候完成,这个
gen.coroutine和Runner可不管,你必须设计一个AsyncHTTPClient中fetch那样的返回Future的异步调用或者用Task封装一下你的带有callback的异步调用。下面是节选gen.coroutine装饰器中主要方法
_make_coroutine_wrapper的代码的主要部分
1 |
try: |
result就是被装饰的函数返回的generator,next启动这个generator, 如果generator抛出StopIteration和Return两个异常,表示generator已经解析出结果,将这个结果设置给最后coroutine返回的
future。如果有其他异常表示generator执行过程中发生了异常,将异常设置到future中。排除这两种情况,表示generator还没有执行完毕,调用Runner执行generator。Runner的参数result就是还没
有运行完毕generator, future是代表coroutine执行结果的那个future, 而yielded是func返回的future(或者YieldPoint,咱们只考虑future的情况)。再深入到Runner中,主要有两个函数handle_yield
和run,handle_yield主要是确定generator返回的yielded是否是一个执行完成的yielded(对于yielded是future的情况来说就是future.is_ready() == True),如果没有执行完成则需要设置future完成时
执行run方法,也就是future.add_done_callback(future, lambda f:self.run())并返回False也就是不执行马上run, 否则返回True并立即执行run方法,因为这时候已经有异步调用的结果了。
run方法拿到yielded的执行结果,并传入到generator中。这样generator内部就能通过yield拿到异步调用的执行结果了。
1 |
def handle_yield(self, yielded): |
分析完毕,没看懂的同学可以在读两遍代码,主要还是要抓住coroutine装饰器只不过是将callback型调用转换成generator型伪同步调用的一个适配器这个关键点,阅读起代码来就明白多了。期待下篇吧,准备
写stack_context异步调用中的异常捕获问题
Tornado源码分析系列之一: 化异步为'同步'的Future和gen.coroutine的更多相关文章
- tornado源码分析系列一
先来看一个简单的示例: #!/usr/bin/env python #coding:utf8 import socket def run(): sock = socket.socket(socket. ...
- MyBatis 源码分析系列文章导读
1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...
- spring源码分析系列 (1) spring拓展接口BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor
更多文章点击--spring源码分析系列 主要分析内容: 一.BeanFactoryPostProcessor.BeanDefinitionRegistryPostProcessor简述与demo示例 ...
- spring源码分析系列 (3) spring拓展接口InstantiationAwareBeanPostProcessor
更多文章点击--spring源码分析系列 主要分析内容: 一.InstantiationAwareBeanPostProcessor简述与demo示例 二.InstantiationAwareBean ...
- spring源码分析系列 (2) spring拓展接口BeanPostProcessor
Spring更多分析--spring源码分析系列 主要分析内容: 一.BeanPostProcessor简述与demo示例 二.BeanPostProcessor源码分析:注册时机和触发点 (源码基于 ...
- Bootstrap源码分析系列之初始化和依赖项
在上一节中我们介绍了Bootstrap整体架构,本节我们将介绍Bootstrap框架第二部分初始化及依赖项,这部分内容位于源码的第8~885行,打开源码这部分内容似乎也不是很难理解.但是请站在一个开发 ...
- tornado源码分析-iostream
tornado源码分析-iostream 1.iostream.py作用 用来异步读写文件,socket通信 2.使用示例 import tornado.ioloop import tornado.i ...
- Netty 源码分析系列(二)Netty 架构设计
前言 上一篇文章,我们对 Netty做了一个基本的概述,知道什么是Netty以及Netty的简单应用. Netty 源码分析系列(一)Netty 概述 本篇文章我们就来说说Netty的架构设计,解密高 ...
- Spring Ioc源码分析系列--Ioc的基础知识准备
Spring Ioc源码分析系列--Ioc的基础知识准备 本系列文章代码基于Spring Framework 5.2.x Ioc的概念 在Spring里,Ioc的定义为The IoC Containe ...
随机推荐
- 可视化SNV安装
以前也安装过非图形化的SVN SERVER,但大多都需要比较复杂的配置,而且操作不太友好,所以其拥有可视化功能就比较重要了. 好了,开始干活吧. 说明:测试机为开发爬虫用的服务器,配置不高 准备工作: ...
- Python文件处理之文件写入方式与写缓存(三)
Python的open的写入方式有: write(str):将str写入文件 writelines(sequence of strings):写多行到文件,参数为可迭代对象 首先来看下writelin ...
- com.opensymphony.module.sitemesh.filter.PageFilter 装饰页面
1.web.xml中配置: <filter> <filter-name>sitemeshFilter</filter-name> <filter-class& ...
- nginx linux 下开机自动启动
这里使用的是编写shell脚本的方式来处理 vi /etc/init.d/nginx (输入下面的代码) #!/bin/bash# nginx Startup script for the Ngin ...
- RMAN备份与恢复之表空间
通过以下事例来说明表空间的恢复,删除表空间内的数据文件,删除后在针对位于该表空间的表进行插入记录以及实施检查点进程 SQL> select a.tablespace_name from dba_ ...
- Microsoft SQL Server Management Studio 导出触发器脚本
- 关于双击事件.MouseEvent.DOUBLE_CLICK
as3提供了双击事件的调用,但有时候碰到双击事件无法响应,所以总结下原因.先摘录一段官方关于 doubleClick 事件发生的条件.如果 InteractiveObject 的 doubleClic ...
- EXECUTE 后的事务计数指示 BEGIN 和 COMMIT 语句的数目不匹配
代码中的事务包含了存储过程中的事务.
- 高性能MySQL --- 读书笔记(2) - 2016/8/2
第1章 MySQL架构 MySQL架构与其他数据库服务器大不相同,这使它能够适应广泛的应用.MySQL足够灵活,能适应高要求架构.例如Web应用,同时还适用于嵌入式应用.数据仓库.内容索引和分发软件. ...
- SSH_框架整合3-删除
一.普通删除 1 完善src中 类: (1)EmployeeDao.java中: //2 删除 public void delete(Integer id){ String hql="DEL ...