http://tornado-zh.readthedocs.io/zh/latest/guide/coroutines.html
Tornado中推荐使用 协程 写异步代码. 协程使用了Python的 yield
关键字代替链式回调来将程序挂起和恢复执行
使用协程几乎像写同步代码一样简单, 并且不需要浪费额外的线程.
from tornado import gen
@gen.coroutine
def fetch_coroutine(url):
http_client = AsyncHTTPClient()
response = yield http_client.fetch(url) #yield是python的关键字,并不是因为@gen.coroutine后才能用。 @gen.coroutine的作用是保持与客户端的连接不断开
# 在Python 3.3之前, 在generator中是不允许有返回值的
# 必须通过抛出异常来代替.
# 就像 raise gen.Return(response.body).
return response.body
Python 3.5 引入了 async
和 await
关键字(使用这些关键字的 函数也被称为”原生协程”).
从Tornado 4.3, 你可以用它们代替 yield
为基础的协程.
只需要简单的使用 async def foo()
在函数定义的时候代替 @gen.coroutine
装饰器, 用 await
代替yield.
本文档的其他部分会继续使用 yield
的风格来和旧版本的Python兼容, 但是如果 async
和 await
可用的话,它们运行起来会更快:
async def fetch_coroutine(url):
http_client = AsyncHTTPClient()
response = await http_client.fetch(url)
return response.body
await
关键字比 yield
关键字功能要少一些. 例如,在一个使用 yield
的协程中, 你可以得到Futures
列表, 但是在原生协程中,你必须把列表用 tornado.gen.multi
包起来. 你也可以使用 tornado.gen.convert_yielded
来把任何使用 yield
工作的代码转换成使用 await
的形式.
包含了 yield
关键字的函数是一个 生成器(generator). 所有的生成器都是异步的; 当调用它们的时候,会返回一个生成器对象,而不是一个执行完的结果.
@gen.coroutine
装饰器通过 yield
表达式和生成器进行交流, 而且通过返回一个 Future
与协程的调用方进行交互.
下面是一个协程装饰器内部循环的简单版本:
# tornado.gen.Runner 简化的内部循环
def run(self):
# send(x) makes the current yield return x.
# It returns when the next yield is reached
future = self.gen.send(self.next)
def callback(f):
self.next = f.result()
self.run()
future.add_done_callback(callback)
装饰器从生成器接收一个 Future
对象, 等待(非阻塞的)这个 Future
对象执行完成, 然后”解开(unwraps)” 这个 Future
对象,并把结果作为 yield
表达式的结果传回给生成器.
如何调用协程
协程一般不会抛出异常: 它们抛出的任何异常将被 Future
捕获 直到它被得到. 这意味着用正确的方式调用协程是重要的, 否则你可能有被 忽略的错误:
@gen.coroutine
def divide(x, y):
return x / y
def bad_call():
# 这里应该抛出一个 ZeroDivisionError 的异常, 但事实上并没有
# 因为协程的调用方式是错误的.
divide(1, 0)
几乎所有的情况下, 任何一个调用协程的函数都必须是协程它自身, 并且在 调用的时候使用 yield
关键字. 当你复写超类中的方法, 请参阅文档, 看看协程是否支持(文档应该会写该方法 “可能是一个协程” 或者 “可能返回 一个 Future
”):
@gen.coroutine
def good_call():
# yield 将会解开 divide() 返回的 Future 并且抛出异常
yield divide(1, 0)