转自:http://strawhatfy.github.io/2015/07/22/Tornado.gen/
引言
注:正文中引用的 Tornado 代码除特别说明外,都默认引用自 Tornado 4.0.1。
tornado.gen 模块是一个基于 python generator 实现的异步编程接口。通过该模块提供的 coroutine (注:这里 coroutine 指的是 ”协程” 概念而不是后面具体实现的 decorator:@gen.decorator),大大简化了在 Tornado 中编写异步代码的工作 —— 支持 “同步方式编写异步代码” ,避免编写烦人的回调函数。参考官方文档的例子,通常我们编写的异步代码如下:
1
|
class AsyncHandler(RequestHandler):
|
而使用 tornado.gen 模块提供的 decorator ,在 Tornado 3.1 以前我们可以这样写异步代码:
1
|
class GenAsyncHandler(RequestHandler):
|
Tornado 3.1 及以上版本,可以直接使用 @gen.coroutine 来代替 @asynchronous:
1
|
class GenAsyncHandler(RequestHandler):
|
注:@asynchronous 在 tornado.web 中定义,对于使用了 @gen.coroutine 装饰的方法不需要再使用 @asynchronous 进行装饰,但同时使用前述 2 个 decorator 进行方法装饰也是合法的,在同时使用的情况下需要注意的是 @asynchronous 必须是第 1 个 decorator 。
很显然,采用同步方式编写的异步代码相比起分散在各处的异步回调函数代码,更利于代码的阅读和逻辑的组织。
该模块的实现非常巧妙也不容易理解,作为阅读 Tonardo 源码的笔记,我将在后面内容中结合源码和自己的理解对其实现进行分析。
@gen.coroutine 与 @gen.engine 的实现原理
tornado.gen 支持以同步方式编写异步代码的核心就是 python generator。其原理简单来说,就是通过 generator.next() 启动 yield 返回的 generator ,通过 IOLoop 与 generator.send(value) 驱动 generator 运行,以达到协调异步执行的目的。
从功能上来看, @gen.coroutine 与 @gen.engine 的功能非常相似,差别就在于二者对被装饰方法参数中的 “callback” 参数处理不一样以及具有不同的返回值。 @gen.coroutine 装饰的方法执行后返回 Future 对象并且会将方法参数中的 “callback” 加入到 Future 完成后的回调列表中;@gen.engine 装饰的方法执行后没有返回值(注:实际上如果被装饰方法有返回值,会抛出 ReturnValueIgnoredError 异常,详见后面的代码分析部分)。
所以,通过 @gen.engine 装饰的方法没有返回值,方法必须自己在异步调用完成后调用 “callback” 来执行回调动作,而通过 @gen.coroutine 装饰的方法则可以直接返回执行结果,然后由 gen 模块负责将结果传递给 “callback” 来执行回调。
注: 从调用者的角度来看 @gen.coroutine
可以视为 @tornado.concurrent.return_future
与 @gen.engine
的组合。
@gen.coroutine 实现原理
@gen.coroutine 中充分利用了 generator 的特性,下面是其实现代码及分析。
1
|
def coroutine(func, replace_callback=True):
|
coroutine 内部直接委托 _make_coroutine_wrapper 完成具体功能(这段代码中 coroutine 的可选参数 “replace_callback” 是没有使用的),返回一个 Future 实例对象。
_make_coroutine_wrapper(func, replace_callback) 函数作为 @gen.coroutine 和 @gen.engine 内部实现,通过 replace_callback 的值来决定是否对 “callback” 方法参数进行处理。coroutine 的实现中通过 replace_callback=True 调用 _make_coroutine_wrapper 函数,会检查方法参数中是否有 “callback” 参数,如果有的话会将其加入到方法返回值 Future 的完成后回调列表中。如下面代码所示:
1
|
def _make_coroutine_wrapper(func, replace_callback):
|
注: 关于 CPython 的 GC 实现,这里有一篇不错的源码分析文章:Python垃圾回收机制
如下面的代码所示, IOLoop 的 add_future 方法会封装回调方法,在 Future 完成以后会将 “callback” 加入到 IOLoop 的回调列表中以等待 IOLoop 调度执行回调动作。
1
|
def add_future(self, future, callback):
|
从上面的代码分析中可以看到 _make_coroutine_wrapper
函数已经完成了 coroutine 的创建,其代码逻辑比较简单,而整个 coroutine 启动、运行的核心功能被实现在 Runner
类中。 Runner
有一个 run()
方法,该方法负责启动 coroutine,并与 IOLoop 配合驱动 YieldPoint(注:在 generator 中通过 yield 返回的实例类型,Tornado 4.0 及以后推荐使用 Futures 类型, YieldPoint 类型被放弃) 执行直到 result_future 完成。 run()
方法的详细代码如下所示:
1
|
def run(self): |