Tornado中推荐使用 协程 编写异步代码,协程使用了Python的 yield 关键字代替链式回调来将程序挂起和恢复执行(像在 gevent 中出现的轻量级线程合作方式有时也被称为协程,但是在Tornado中所有的协程使用明确的上下文切换,并被称为异步函数)。
使用协程几乎像写同步代码一样简单,并且不需要浪费额外的线程。它们还通过减少上下文切换来 使并发编程更简单 。
例子:
| 1 | from tornado import gen | 
Python 3.5: async 和 await
Python 3.5 引入了 async 和 await 关键字(使用这些关键字的函数也被称为”原生协程”)。从Tornado 4.3,你可以用它们代替 yield 为基础的协程。只需要简单的使用 async def foo() 在函数定义的时候代替 `@gen.coroutine装饰器,用await代替 yield。本文档的其他部分会继续使用yield的风格来和旧版本的Python兼容,但是如果async和await` 可用的话,它们运行起来会更快:
| 1 | async def fetch_coroutine(url): | 
await 关键字比 yield 关键字功能要少一些。例如,在一个使用 yield 的协程中,你可以得到 Futures 列表,但是在原生协程中,你必须把列表用 tornado.gen.multi 包起来。同时也去掉了与 concurrent.futures 的集成。你也可以使用 tornado.gen.convert_yielded 来把任何使用 yield 工作的代码转换成使用 await 的形式:
| 1 | async def f(): | 
它如何工作
包含了 yield 关键字的函数是一个 生成器(generator) 。所有的生成器都是异步的;当调用它们的时候,会返回一个生成器对象,而不是一个执行完的结果。 `@gen.coroutine装饰器通过yield表达式和生成器进行交流,而且通过返回一个 [Future`](http://tornado-zh.readthedocs.io/zh/latest/concurrent.html#tornado.concurrent.Future) 与协程的调用方进行交互。
下面是一个协程装饰器内部循环的简单版本:
| 1 | # Simplified inner loop of tornado.gen.Runner | 
装饰器从生成器接收一个 Future 对象,等待(非阻塞的)这个 Future 对象执行完成,然后“解开(unwraps)” 这个 Future 对象,并把结果作为 yield 表达式的结果传回给生成器。大多数异步代码从来不会直接接触 Future 类 除非 Future 立即通过异步函数返回给 yield 表达式。
如何调用协程
协程一般不会抛出异常:它们抛出的任何异常将被 Future 捕获直到它被得到,这意味着用正确的方式调用协程是重要的,否则你可能有被忽略的错误:
| 1 | @gen.coroutine | 
几乎所有的情况下,任何一个调用协程的函数都必须是协程它自身,并且在调用的时候使用 yield 关键字。当你重写超类中的方法,请参阅文档,看看协程是否支持(文档应该会写该方法 “可能是一个协程” 或者 “可能返回 一个 Future ”):
| 1 | @gen.coroutine | 
有时你可能想要对一个协程”一劳永逸”而且不等待它的结果。在这种情况下,建议使用 IOLoop.spawn_callback ,它使得 IOLoop 负责调用。如果它失败了, IOLoop 会在日志中把调用栈记录下来:
| 1 | # The IOLoop will catch the exception and print a stack trace in | 
如果函数使用了 `@gen.coroutine,则推荐以上方式使用 [IOLoop.spawn_callback](http://www.tornadoweb.org/en/stable/ioloop.html#tornado.ioloop.IOLoop.spawn_callback) ,但如果函数使用async def` ,则必须使用以上方式(否则协程不会运行)。
最后,在程序顶层,如果 IOLoop 尚未运行,你可以启动 IOLoop 执行协程,然后使用 IOLoop.run_sync 方法停止 IOLoop 。这通常被用来启动面向批处理程序的 main 函数:
| 1 | # run_sync() doesn't take arguments, so we must wrap the | 
协程模式
调用阻塞函数
从一个协程调用阻塞函数最简单的方式是使用 IOLoop.run_in_executor ,它将返回和协程兼容的Futures :
| 1 | @gen.coroutine | 
并行
协程装饰器能识别列表或者字典对象中各自的 Futures ,并且并行的等待这些 Futures :
| 1 | @gen.coroutine | 
如果使用 await 关键字,列表和字典必须使用 tornado.gen.multi 包装起来:
| 1 | async def parallel_fetch(url1, url2): | 
交叉存取
有时候我们需要保存Future 而不是立即返回,所以可以在等待之前执行其他操作:
| 1 | @gen.coroutine | 
这种模式最适用于 `@gen.coroutine,如果fetch_next_chunk()使用async def,则必须如下调用:fetch_future = tornado.gen.convert_yielded(self.fetch_next_chunk())`才能启动后台进程。
循环
在原生协程中,可以使用aysnc for 。在老版本的Python中,协程的循环是棘手的,因为没有办法在 for 循环或者 while 循环 yield 迭代器,并且捕获 yield 的结果。相反,你需要将循环条件从访问结果中分离出来,下面是一个使用 Motor 的例子:
| 1 | import motor | 
在后台运行
PeriodicCallback 通常不使用协程。相反,一个协程可以包含一个 while True: 循环并使用tornado.gen.sleep :
| 1 | @gen.coroutine | 
有时可能会遇到一个更复杂的循环。例如,上一个循环运行每次花费 60+N 秒,其中 N 是 do_something() 花费的时间。为了 准确的每60秒运行,使用上面的交叉模式:
| 1 | @gen.coroutine | 
Read More: