博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

tornado 异步,协程

Posted on 2017-11-03 18:16  bw_0927  阅读(236)  评论(0)    收藏  举报

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 类 除非 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)