Tornado @tornado.gen.coroutine 与 yield
在使用 Tornado 的过程中产生了以下疑问:
- 什么时候需要给函数增加 @tornado.gen.coroutine
- 什么时候调用函数需要 yield
@tornado.gen.coroutine 与 yield 是如何工作的
包含 yield 的函数是一个 generator[1]。@gen.coroutine 通过 yield 与 generator 沟通、通过返回 Future 与协程的调用者沟通。
具体沟通情况:
- @gen.coroutine收到从 generator 返回的- Future
- "unwraps" Future获得结果
- 将结果发送回 generator 以作为 yield表达式的结果
如何调用协程
上文提到,
@gen.coroutine通过返回Future与协程的调用者沟通
所以我们必须 "unwraps" 这个 Future 才能得到结果。
所以在绝大部分情况下,任何调用协程的函数本身必须是一个协程,并且在调用中要使用 yield。
@gen.coroutine
def good_call():
    # yield will unwrap the Future returned by divide() and raise
    # the exception.
    yield divide(1, 0)
注意,yield 只有在 @gen.coroutine 中才会 "unwraps" 一个 Future,如果没有 @gen.coroutine,那么 yield 只会将该函数变为一个而普通的生成器,比如下面两个例子。
- 错误的
import tornado.gen
from tornado.ioloop import IOLoop
from tornado.gen import Return
@tornado.gen.coroutine
def call_me():
    raise Return('result')
def f():
    r = yield call_me()
    print(r)  # tornado.gen.BadYieldError: yielded unknown object <generator object f at 0x104a34320>
IOLoop.current().run_sync(f)
错误的原因是:run_sync 会调用 f(),然后尝试将 f() 的结果转换为 Future,转换的函数如下:
# env/lib/python2.7/site-packages/tornado/gen.py:1259
def convert_yielded(yielded):
    """Convert a yielded object into a `.Future`.
    The default implementation accepts lists, dictionaries, and Futures.
    If the `~functools.singledispatch` library is available, this function
    may be extended to support additional types. For example::
        @convert_yielded.register(asyncio.Future)
        def _(asyncio_future):
            return tornado.platform.asyncio.to_tornado_future(asyncio_future)
    .. versionadded:: 4.1
    """
    # Lists and dicts containing YieldPoints were handled earlier.
    if yielded is None:
        return moment
    elif isinstance(yielded, (list, dict)):
        return multi(yielded)
    elif is_future(yielded):
        return yielded
    elif isawaitable(yielded):
        return _wrap_awaitable(yielded)
    else:
        raise BadYieldError("yielded unknown object %r" % (yielded,))
由于 f() 返回的是一个 generator 对象,不符合转换的要求,所以报错。如果给 f() 加上 @tornado.gen.coroutine,那么装饰器会将 f() 返回的结果转换为 Future,符合 elif is_future(yielded):,也就能顺利运行。
- 正确的
import tornado.gen
from tornado.ioloop import IOLoop
from tornado.gen import Return
@tornado.gen.coroutine
def call_me():
    raise Return('result')
@tornado.gen.coroutine
def f():
    r = yield call_me()
    print(r)  # result
IOLoop.current().run_sync(f)
总结
- 当调用一个协程时,@tornado.gen.coroutine与yield必须同时出现调用函数中
- 如果只是在协程中执行操作或者直接返回结果,有 @tornado.gen.coroutine和 return(raise Return)就够了
 
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号