Reputation: 347
I have a piece of code as follows:
@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
If the order of asynchronous and gen.coroutine is as shown in my code, it works fine.
But if I switch the order of them, the client side can only receive 8MB data. the traceback.print_exc()
prints finish() called twice. May be caused by using async operations without the @asynchronous decorator.
So my question, why does the order of these two decorators matter, and what are the rules to choose the order?
Upvotes: 2
Views: 1242
Reputation: 22154
Order matters because @asynchronous
looks at the Future
returned by @gen.coroutine
, and calls finish
for you when the coroutine returns. Since Tornado 3.1, the combination of @asynchronous
and @gen.coroutine
has been unnecessary and discouraged; in most cases you should use @gen.coroutine
alone.
However, the example you've shown here is kind of odd - it mixes the coroutine and callback style in ways that don't really work well. The coroutine returns before it is finished, and leaves the remaining work to a chain of callbacks. This is actually similar in spirit to what the @asynchronous
decorator does with non-coroutine functions, although it doesn't work with the interaction between the two decorators. The best solution here is to make loop()
a coroutine too:
@gen.coroutine
def loop(self):
for data in self.generator:
self.write(data)
yield self.flush()
Then you can call it with yield self.loop()
and the coroutine will work normally. You no longer need to call finish()
explicitly, or use the @asynchronous
decorator.
Upvotes: 2