Is this a recommended method to handle blocking tasks in Tornado?

1,029 views
Skip to first unread message

Shawn Zhuang

unread,
Jan 8, 2015, 4:38:00 AM1/8/15
to python-...@googlegroups.com
Hi, there

I am writing a web API using Tornado(version 4.1) , but I  suffered a lot of blocking problems. After searching a lot of tech blogs, I find a method as follow:

But I still have some questions need your help:
    1. The code can realize a asynchronous job, but is it a good way? What happened to tornado when the task is submitted?
    2. Tornado is single thread, so this is a new thread pool in this main thread? should the thread pool be global or local for the handle class?
    3. If I send a request from Firefox the app seems to be not asynchronous ,but if I send a request from python ,it is asynchronous , so wired! Why?

I have tested the code, it works well, and I hope this will help more people who are finding asynchronous methods. But I really hope professional guys
to answer above questions.

Thank you very much! 


 
import tornado.web
from tornado.concurrent import run_on_executor
from concurrent.futures import ThreadPoolExecutor
import time

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world %s" % time.time())


class SleepHandler(tornado.web.RequestHandler):
    @property
    def executor(self):
        return self.application.executor

    @tornado.gen.coroutine
    def get(self, n):
        n = yield self._exe(n)
        self.write("Awake! %s" % time.time())
        self.finish()

    @run_on_executor
    def _exe(self, n):
        """
        This is a long time job and may block the server,such as a complex DB query or Http request.
        """
        time.sleep(float(n))
        return n


class App(tornado.web.Application):
    def __init__(self):
        handlers = [
                        (r"/", MainHandler),
                        (r"/sleep/(\d+)", SleepHandler),
                    ]
        tornado.web.Application.__init__(self, handlers)
        self.executor = ThreadPoolExecutor(max_workers=60)


if __name__ == "__main__":
    application = App()
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

Andrei Taranchenko

unread,
Jan 9, 2015, 1:30:04 PM1/9/15
to python-...@googlegroups.com
Yes, this seems like a good way to deal with this. 

Note that some tasks may appear to be blocking, when in fact they are not. I possibly had a similar problem here:


A good way to test, in your case, is to put a hard "sleep" in the code that you are not sure about, and access the system from another client (as opposed to the same browser).

Ben Darnell

unread,
Jan 10, 2015, 10:44:29 AM1/10/15
to Tornado Mailing List
On Thu, Jan 8, 2015 at 4:38 AM, Shawn Zhuang <master...@gmail.com> wrote:
Hi, there

I am writing a web API using Tornado(version 4.1) , but I  suffered a lot of blocking problems. After searching a lot of tech blogs, I find a method as follow:

But I still have some questions need your help:
    1. The code can realize a asynchronous job, but is it a good way? What happened to tornado when the task is submitted?

Yes, this is a good way. A ThreadPoolExecutor is the recommended way to use blocking functions that cannot be easily rewritten as non-blocking. When you call `yield self._exe(n)`, that handler will be suspended and the main thread will return to the HTTPServer so it is free to handle other requests. The suspended handler will wake up when the task is completed and the IOLoop is not busy.
 
    2. Tornado is single thread, so this is a new thread pool in this main thread? should the thread pool be global or local for the handle class?

The new thread pool is created by the main thread but is not "in" the thread. Thread pools should generally be either global or class variables; not instance variables. It is a good practice to have one thread pool for each kind of resource: e.g. one thread pool for database queries and second pool for image processing. This lets you set and monitor separate limits for each one.
 
    3. If I send a request from Firefox the app seems to be not asynchronous ,but if I send a request from python ,it is asynchronous , so wired! Why?

Browsers will generally not let you make more than one request at a time to the same url. If you use different urls in firefox you should see multiple requests at once (but only a few requests at a time to a given domain)

-Ben
 

--
You received this message because you are subscribed to the Google Groups "Tornado Web Server" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python-tornad...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Shawn Zhuang

unread,
Jan 12, 2015, 1:44:31 AM1/12/15
to python-...@googlegroups.com, b...@bendarnell.com
Hi,Ben

Thank you for your answer! But some tech blogs are warning that Tornado' s IOLoop is a single thread and may still blocking even the request thread is in a new thread, is that true?
For example, the request is a Mysql query need 5 seconds, so in my code, will this still block the server? Creating a new thread , but there is no call back function defined, how IOLoop handle
the asyn request?

在 2015年1月10日星期六 UTC+8下午11:44:29,Ben Darnell写道:

Ben Darnell

unread,
Jan 12, 2015, 9:35:56 AM1/12/15
to Shawn Zhuang, Tornado Mailing List
As long as you run your database queries on the executor they will not block the main thread. The callback function is created automatically by the combination of the @coroutine decorator and the yield keyword.

-Ben
Reply all
Reply to author
Forward
0 new messages