@gen.coroutine
是 Tornado 3 的新功能。来自http://www.tornadoweb.org/en/stable/releases/v3.0.0.html:
新的装饰器 @gen.coroutine 可作为 @gen.engine 的替代品。它会自动返回一个 Future,并且在函数中而不是调用回调,而是使用 raise gen.Return(value) 返回一个值(或者在 Python 3.3 中简单地返回值)。
从文档(http://www.tornadoweb.org/en/stable/gen.html#tornado.gen.coroutine):
带有这个装饰器的函数返回一个 Future。此外,可以使用回调关键字参数调用它们,当它解析时将使用未来的结果调用它们。如果协程失败,回调将不会运行,并且会在周围的 StackContext 中引发异常。回调参数在装饰函数中不可见;它由装饰器本身处理。
因此没有理由担心回调,也没有必要将函数包装到tornado.gen.Task()
. 链接现在很容易:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
import tornado.gen
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class MainHandler(tornado.web.RequestHandler):
@tornado.gen.coroutine
def outer(self):
logging.info('outer starts')
yield self.inner()
yield self.inner()
logging.info('outer ends')
raise tornado.gen.Return('hello')
@tornado.gen.coroutine
def inner(self):
logging.info('inner runs')
@tornado.web.asynchronous
@tornado.gen.coroutine
def get(self):
res = yield self.outer()
self.write(res)
if __name__ == "__main__":
tornado.options.parse_command_line()
app = tornado.web.Application(handlers=[(r"/", MainHandler)])
http_server = tornado.httpserver.HTTPServer(app)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
输出:
$ python test.py
[I 130529 03:18:35 test:21] outer starts
[I 130529 03:18:35 test:29] inner runs
[I 130529 03:18:35 test:29] inner runs
[I 130529 03:18:35 test:24] outer ends
[I 130529 03:18:35 web:1514] 200 GET / (127.0.0.1) 1.48ms
从 Python 3.3 开始不需要使用gen.Result()
,简单return
就可以了。在旧版本中,会出现'return' with argument inside generator
错误。
另外,检查:https ://github.com/facebook/tornado/issues/759
更新:
至于 Tornado 2.x,我认为没有简单的方法可以隐藏回调。文档指出:
在大多数情况下,用引擎装饰的函数应该接受一个回调参数,并在完成时使用其结果调用它。一个值得注意的例外是 RequestHandler get/post/etc 方法,它使用 self.finish() 代替回调参数。
所以恐怕这些都是不可避免的。例子:
class MainHandler(tornado.web.RequestHandler):
@tornado.web.asynchronous
@tornado.gen.engine
def get(self):
res = yield tornado.gen.Task(self.outer)
self.write(res)
self.finish()
def inner(self, callback):
logging.info('inner runs')
callback()
@tornado.gen.engine
def outer(self, callback):
logging.info('outer starts')
yield tornado.gen.Task(self.inner)
yield tornado.gen.Task(self.inner)
logging.info('outer ends')
callback("hello")