我有一段代码如下:
@tornado.web.stream_request_body
class DownloadHandler(SecureHandler):
executor = ThreadPoolExecutor(50)
@tornado.web.authenticated
@tornado.gen.coroutine
@tornado.gen.asynchronous
def post(self):
# ...
path = yield self.down_load(fname)
self.set_header("Content-Type", "application/octet-stream")
self.set_header("Content-Disposition", "attachment;filename=%s" % fname)
self.generator = self.read_file(path)
tornado.ioloop.IOLoop.instance().add_callback(self.loop)
@run_on_executor
def down_load(self, fname):
# download a file named `fname` from other website
# store it in a temp file at `path`
# ...
return path
def loop(self):
try:
data = self.generator.next()
self.write(data)
self.flush()
tornado.ioloop.IOLoop.instance().add_callback(self.loop)
except Exception as e:
traceback.print_exc()
self.finish()
def read_file(self, fname):
with open(fname, 'rb') as f:
while True:
data = f.read(1024 * 1024 * 8)
if not data
break
yield data
如果异步和 gen.coroutine 的顺序如我的代码所示,则工作正常。
但是如果我调换它们的顺序,客户端只能接收8MB数据。 traceback.print_exc()
打印调用两次的 finish() 。可能是由于在没有 @asynchronous 装饰器的情况下使用异步操作引起的。
所以我的问题是,为什么这两个装饰器的顺序很重要,选择顺序的规则是什么?
最佳答案
顺序很重要,因为@asynchronous
会查看@gen.coroutine
返回的Future
,并调用finish
当协程返回时为您服务。从 Tornado 3.1 开始,@asynchronous 和 @gen.coroutine 的组合已经没有必要并且不鼓励了;在大多数情况下,您应该单独使用 @gen.coroutine
。
但是,您在这里展示的示例有点奇怪 - 它以不太有效的方式混合了协程和回调风格。协程在完成之前返回,并将剩余的工作留给回调链。这实际上与 @asynchronous 装饰器对非协程函数所做的事情类似,尽管它不适用于两个装饰器之间的交互。这里最好的解决方案是让 loop()
也成为一个协程:
@gen.coroutine
def loop(self):
for data in self.generator:
self.write(data)
yield self.flush()
然后你可以用yield self.loop()
调用它,协程就会正常工作。您不再需要显式调用 finish()
或使用 @asynchronous
装饰器。
关于python - 为什么异步和 gen.coroutine 的顺序在 Tornado 中很重要?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27393059/