When I run this handler in a simple Tornado app and make two requests to it with curl
, it doesn't run in parallel. It prints out "1 2 3 4 5 1 2 3 4 5", when I want it to print "1 1 2 2 3 3 4 4 5 5".
class SleepHandler(RequestHandler):
def get(self):
for i in range(5):
print(i)
time.sleep(1)
What am I doing wrong?
The reason for this is that time.sleep
is a blocking function: it doesn’t allow control to return to the IOLoop
so that other handlers can be run.
Of course, time.sleep
is often just a placeholder in these examples, the point is to show what happens when something in a handler gets slow. No matter what the real code is doing, to achieve concurrency blocking code must be replaced with non-blocking equivalents. This means one of three things:
Find a coroutine-friendly equivalent. For time.sleep, use tornado.gen.sleep instead:
class CoroutineSleepHandler(RequestHandler):
@gen.coroutine
def get(self):
for i in range(5):
print(i)
yield gen.sleep(1)
When this option is available, it is usually the best approach. See the Tornado wiki for links to asynchronous libraries that may be useful.
Find a callback-based equivalent. Similar to the first option, callback-based libraries are available for many tasks, although they are slightly more complicated to use than a library designed for coroutines. These are typically used with tornado.gen.Task
as an adapter:
class CoroutineTimeoutHandler(RequestHandler):
@gen.coroutine
def get(self):
io_loop = IOLoop.current()
for i in range(5):
print(i)
yield gen.Task(io_loop.add_timeout, io_loop.time() + 1)
Again, the Tornado wiki can be useful to find suitable libraries.
Run the blocking code on another thread. When asynchronous libraries are not available, concurrent.futures.ThreadPoolExecutor
can be used to run any blocking code on another thread. This is a universal solution that can be used for any blocking function whether an asynchronous counterpart exists or not:
executor = concurrent.futures.ThreadPoolExecutor(8)
class ThreadPoolHandler(RequestHandler):
@gen.coroutine
def get(self):
for i in range(5):
print(i)
yield executor.submit(time.sleep, 1)
See the Asynchronous I/O chapter of the Tornado user’s guide for more on blocking and asynchronous functions.