Leagsaidh Gordon
Leagsaidh Gordon

Reputation: 1701

Tornado non-blocking request

I have a function that takes a while to run, and need to run it in a request. What is the best way to deal with this so that this request does not block the main thread while it is being processed? I looked at the @tornado.web.asynchronous decorator, but that isn't much use here when the function isn't an async tornado module.

class LongHandler(tornado.web.RequestHandler):
    def get(self):
        self.write(self.long_time_function())

    def long_time_function(self):
        time.sleep(5)
        return "foo"

Upvotes: 3

Views: 2512

Answers (2)

abarnert
abarnert

Reputation: 366013

When you have some blocking task that doesn't play nice with the async event loop, you have to put it in a separate thread.

If you're going to have an unbounded number of blocking tasks, you want to use a thread pool.

Either way, you want to have a wrapper async task that blocks on notification from the threaded task.

The easiest way to do this is to use a pre-built library like tornado-threadpool.* Then, you just do something like this:

class LongHandler(tornado.web.RequestHandler):
    @thread_pool.in_thread_pool
    def long_time_function(self, callback):
        time.sleep(5)
        callback("foo")

If you want to do it yourself, this gist shows an example of what you have to do—or, of course, the source code for the various Tornado threadpool libraries can serve as sample code.

Just keep in mind the limitations of the Python GIL: If your background task is CPU-bound (and does most of the work in Python, rather than in a C extension that releases the GIL like numpy), you have to put it in a separate process. A quick search for Tornado process pool libraries didn't turn up as many nice options, but adapting thread pool code to process pool code is usually very easy in Python.**


* Note that I'm not specifically recommending that library; it's just the first thing that came up in a Google search, and it looks usable and correct from a quick glance.

** It's often as simple as replacing concurrent.futures.ThreadPoolExecutor with concurrent.futures.ProcessPoolExecutor or multiprocessing.dummy.Pool with multiprocessing.Pool. The only trick is to make sure that all of your task arguments and return values are small and pickleable.

Upvotes: 7

Nykakin
Nykakin

Reputation: 8747

Using another thread as stated in other answer is one way to go. If your function can be splitted into fragments (for example, there is a loop with many iterations inside) you can also split it using IOLoop.add_callback() to intertwine computing it with processing other requests. Here is an example how to do this: why my coroutine blocks whole tornado instance?

Upvotes: 1

Related Questions