Tornado doesn't work asynchronously

2019-08-16 12:13发布

问题:

I am trying to write a non-blocking api with tornado. But when I try it on local the first request is blocking the API. I tried to use different browsers but the result is same.

I opened chrome and firefox. On chrome I go http://localhost:8888/test-first and while it is loading I immediatly go http://localhost:8888/test-second from firefox.

I am expecting the request from firefox would be answered while the other one is still loading. But both of them are waiting and when the first request is done both of them finishes.

My codes and output:

Expecting console output:

First request: 2017-11-22 22:23:22.093497
Second request: 2017-11-22 22:23:24.580052
Second answer: 2017-11-22 22:23:25.580509
First answer: 2017-11-22 22:23:37.579263

Console Output

First request: 2017-11-22 22:23:22.093497
First answer: 2017-11-22 22:23:37.579263
Second request: 2017-11-22 22:23:37.580052
Second answer: 2017-11-22 22:23:37.580509

test_first.py:

import tornado.web
import datetime


class First(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    async def get(self):
        print("First request: " + str(datetime.datetime.now()))
        for _ in range(1000000000):
            pass
        self.set_status(200)
        self.write("OK")
        self.finish()
        print("First answer: " + str(datetime.datetime.now()))

test_second.py:

import tornado.web
import datetime


class Second(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    async def get(self):
        print("Second request: " + str(datetime.datetime.now()))
        self.set_status(200)
        self.write("OK")
        self.finish()
        print("Second answer: " + str(datetime.datetime.now()))

app.py:

import tornado
from test_first import First
from test_second import Second


class Application(tornado.web.Application):
    def __init__(self):
        ENDPOINTS = [
            # USERS #
            (r"/test-first", First),
            (r"/test-second", Second)
        ]

        SETTINGS = {
            "debug": True,
            "autoreload": True,
            "serve_traceback": True,
            "compress_response": True
        }

        tornado.web.Application.__init__(self, ENDPOINTS, SETTINGS)


if __name__ == "__main__":
    print("dinliyor...")
    Application().listen(8888)
    tornado.ioloop.IOLoop.instance().start()

回答1:

for _ in range(1000000000):
        pass

This is a CPU intensive task. So, it blocks the whole server. Tornado, and almost every other async library, is for doing asynchronous network I/O, which means, data comes in, data goes out - no heavy CPU tasks.

To perform a CPU bound blocking task, you'll have to run it in a separate process, or thread so that it doesn't block the server.

But anyway, to get the output as you expect, you can pause the First handler using tornado.gen.sleep.

class First(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    async def get(self):
        print("First request: " + str(datetime.datetime.now()))

        await tornado.gen.sleep(5) # sleep for 5 seconds

        self.set_status(200)
        ...