Running (possibly) blocking code like a Tornado coroutine
One of the main benefits of using the Tornado web server is that it is (normally) a single-threaded, asynchronous framework that can rely on coroutines for concurrency. Many drivers already exist to provide a client library utilizing the Tornado event loop and coroutines (e.g., the Motor MongoDB driver).
To write your own coroutine-friendly code for Tornado, there are a few different options available, all requiring that you somehow wrap blocking calls within a Future so as to allow the event loop to continue executing. Here, I demonstrate one recipe to do just this by utilizing Executor objects from the concurrent.futures module. We start with the imports:
import random
import time
from tornado import gen
from tornado.concurrent import run_on_executor, futures
from tornado.ioloop import IOLoopWe will be using the run_on_executor decorator which requires that the class whose methods we decorate have some type of Executor attribute (the default is to use the executor attribute, but a different Executor can be used with a keyword argument passed to the decorator). We’ll create a class to run our asynchronous tasks and give it a ThreadPoolExecutor for executing tasks. In this contrived example, our long running task just sleeps for a random amount of time:
class TaskRunner(object):
def __init__(self, loop=None):
self.executor = futures.ThreadPoolExecutor(4)
self.loop = loop or IOLoop.instance()
@run_on_executor
def long_running_task(self):
tau = random.randint(0, 3)
time.sleep(tau)
return tauNow, from within a coroutine, we can let the tasks run as if they were normal coroutines:
loop = IOLoop() # this is necessary if running as an ipynb!
tasks = TaskRunner(loop)
@gen.coroutine
def do_stuff():
result = yield tasks.long_running_task()
raise gen.Return(result)
def do_other_stuff():
print(random.random())Finally, in the main coroutine:
@gen.coroutine
def main():
for i in range(10):
stuff = yield do_stuff()
print(stuff)
do_other_stuff()
loop.run_sync(main)Which produces output like:
3
0.6012166386789509
1
0.9235652108721132
0
0.42316507955015026
3
0.9766563871068523
1
0.21032495467534018
2
0.15572313672917715
0
0.8767039780374377
3
0.6542727048597389
2
0.3623342196737247
0
0.30042493880819876
Using this general pattern, it is rather easy to adapt blocking calls to Tornado’s coroutines. Note that the example code can be found here.