简体   繁体   中英

Tornado Coroutine - Custom function

I'm in the process of understanding coroutine from Tornado so let's keep everything simple and the more code you paste, the better.

What I want is to make my homemade function async.

All examples I could find in the documentation falls under the same "hidden" part: AsyncHTTPClient. I'm not looking to do an HTTP call. So please do NOT give me example with that class. I'm interested to create something from scratch. I've tried all possibilities on Tornado coroutine

For now I've been testing with a bash sleep. Here is the code:

import tornado.web
import tornado.httpserver
import tornado.gen
import tornado.concurrent
import subprocess
import os

@tornado.gen.coroutine
def letswait():
    fut = tornado.concurrent.Future()
    subprocess.check_output(["sleep", "5"])
    fut.set_result(42)
    return fut

class TestHandler1(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        value = yield letswait()
        self.render("test.html", num=value)

class TestHandler2(tornado.web.RequestHandler):
    def get(self):
        self.render("test.html", num=66)

class Application(tornado.web.Application):
    def __init__(self):
        DIRNAME = os.path.dirname(__file__)
        STATIC_PATH = os.path.join(DIRNAME, '../static')
        TEMPLATE_PATH = os.path.join(DIRNAME, '../template')
        sets = {
            "template_path":TEMPLATE_PATH,
            "static_path":STATIC_PATH,
            "debug":True,
        }
        tornado.web.Application.__init__(self, [
            (r"/test1", TestHandler1),
            (r"/test2", TestHandler2),
        ], **sets)

def main():
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(8888)
    print "Let s start"
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()

But if I visit test1 then I need to wait for the call to return before I can access test2. From what I've understood, I need to use gen.sleep(5) . But that's just an example. Let's say instead of running sleep 5 on bash, I'm running ssh somewhere 'do_something' which takes some time to run.

I've been told "this function isn't asynchronous". So my question is how do I make a custom function asynchronous?

EDIT: After searching a little, I've seen there is tornado.process https://gist.github.com/FZambia/5756470 to be used here. But my subprocess comes from a 3rd party, so it's not really something I can overwrite. So my question is also, how do I integrate 3rd party libraries with that gen.coroutine system?

SOLUTION: Thanks to the comments below I've got a solution:

import tornado.web
import tornado.httpserver
import tornado.gen
import tornado.concurrent
import subprocess
import os

from concurrent import futures

# Create a threadpool, and this can be shared around different python files
# which will not re-create 10 threadpools when we call it.
# we can a handful of executors for running synchronous tasks

# Create a 10 thread threadpool that we can use to call any synchronous/blocking functions
executor = futures.ThreadPoolExecutor(10)

def letswait():
    result_future = tornado.concurrent.Future()
    subprocess.check_output(["sleep", "5"])
    result_future.set_result(42)
    return result_future

class TestHandler1(tornado.web.RequestHandler):
    @tornado.gen.coroutine
    def get(self):
        value = yield executor.submit(letswait)
        self.render("test.html", num=value)

class TestHandler2(tornado.web.RequestHandler):
    def get(self):
        self.render("test.html", num=66)

class Application(tornado.web.Application):
    def __init__(self):
        DIRNAME = os.path.dirname(__file__)
        STATIC_PATH = os.path.join(DIRNAME, '../static')
        TEMPLATE_PATH = os.path.join(DIRNAME, '../template')
        sets = {
            "template_path":TEMPLATE_PATH,
            "static_path":STATIC_PATH,
            "debug":True,
        }
        tornado.web.Application.__init__(self, [
            (r"/test1", TestHandler1),
            (r"/test2", TestHandler2),
        ], **sets)

def main():
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(8888)
    print "Let s start"
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()

I've asked a similar question here: Python Tornado - Confused how to convert a blocking function into a non-blocking function

The issue is that your function might be CPU bound, and the only way is to use an executor.

from concurrent import futures

# Create a threadpool, and this can be shared around different python files
# which will not re-create 10 threadpools when we call it.
# we can a handful of executors for running synchronous tasks

# Create a 10 thread threadpool that we can use to call any synchronous/blocking functions
executor = futures.ThreadPoolExecutor(10)

Then you could do something like:

@gen.coroutine
def get(self):
    json = yield executor.submit(some_long_running_function)

This task will be set aside, and run independently, since there is a yield keyword, tornado will do some other thing while doing a pure thread switch between what it is currently running and your process. It seems to be work fine for me.

In other word, you can wrap the subprocess in the executor, and it will be processed asynchronously.

If you don't want to use an executor, it seems that your functions need be implemented in a state machine way.

Another article: https://emptysqua.re/blog/motor-internals-how-i-asynchronized-a-synchronous-library/

Notice that Momoko (Postgres), and Motor (MongoDB), are all I/O Bound.

Edit: I'm not sure what are your uses for Tornado. I use Tornado when I do a lot of I/O, because I'm I/O bound. However, I guess if your uses are more CPU bound, you might want to look at Flask. You can easily use Gunicorn and Flask to create something simple, and utilize multiple cores. Trying to use multithread or multicore in Tornado can cause you a lot of headaches since a lot of things in Tornado are not thread safe.

Edit 2: Removed the .result() call.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM