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)就够了