Tornado blokuje żądania asynchroniczne

Używając Tornado, mam żądanie Get, które zajmuje dużo czasu, ponieważ wysyła wiele żądań do innej usługi internetowej i przetwarza dane, Może potrwać kilka minut, aby w pełni zakończyć. Nie chcę, aby blokowało to cały serwer WWW od odpowiadania na inne żądania, co obecnie robi.

Jak rozumiem, Tornado jest jednowątkowe i wykonuje każde żądanie synchronicznie, mimo że obsługuje je asynchronicznie (nadal jest zdezorientowany na tym bitie). Są części długiego procesu to mogą być punkty pauzy, aby umożliwić serwerowi obsługę innych żądań (możliwe rozwiązanie?). Uruchamiam go na Heroku z jednym workerem, więc nie wiem, jak to się przekłada na zrobienie nowego wątku lub wieloprocesora, w którym nie mam doświadczenia z Pythonem.

Oto, co próbuję zrobić: klient wykonuje wywołanie get, aby rozpocząć proces, a następnie co 5 sekund przeplatam kolejne wywołanie get, aby sprawdzić status i zaktualizować stronę o nowe informacje (długie ankiety również pracy, ale natrafia na ten sam problem). Problem polega na tym, że rozpoczęcie długiego procesu blokuje wszystkie nowe żądania get (lub nowe długie sesje ankietowe), aż do zakończenia.

Czy jest łatwy sposób, aby rozpocząć to długie połączenie get i nie zablokować całego serwera www w procesie? Czy jest coś, co mogę umieścić w kodzie, aby powiedzieć?. "pauza, idź obsługiwać oczekujące żądania, a następnie kontynuować"?

Muszę zainicjować żądanie get na ProcessHandler. Następnie muszę nadal być w stanie zapytać StatusHandler podczas uruchamiania ProcessHandler.

Przykład:

class StatusHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
       self.render("status.html")

class ProcessHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    def get(self):
       self.updateStatus("0")
       result1 = self.function1()
       self.updateStatus("1")
       result2 = self.function2(result1)
       self.updateStatus("2")
       result3 = self.function3(result2)
       self.updateStatus("3")
       self.finish()
Author: JeffG, 2012-10-24

2 answers

Oto pełna przykładowa aplikacja Tornado, która używa klienta HTTP Async i modułu gen.Task, Aby uprościć sprawy.

Jeśli przeczytasz więcej o gen.Task W dokumentach, zobaczysz, że możesz wysyłać wiele żądań w tym samym czasie. To jest przy użyciu podstawowej idei Tornado, gdzie wszystko nie jest blokowanie i nadal utrzymuje jeden proces.

Aktualizacja: dodałem obsługę wątku, aby zademonstrować, jak można wysłać pracę do drugiego wątku i otrzymać callback() Kiedy to się skończy.

import os
import threading
import tornado.options
import tornado.ioloop
import tornado.httpserver
import tornado.httpclient
import tornado.web
from tornado import gen
from tornado.web import asynchronous

tornado.options.define('port', type=int, default=9000, help='server port number (default: 9000)')
tornado.options.define('debug', type=bool, default=False, help='run in debug mode with autoreload (default: False)')

class Worker(threading.Thread):
   def __init__(self, callback=None, *args, **kwargs):
        super(Worker, self).__init__(*args, **kwargs)
        self.callback = callback

   def run(self):
        import time
        time.sleep(10)
        self.callback('DONE')

class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/", IndexHandler),
            (r"/thread", ThreadHandler),
        ]
        settings = dict(
            static_path = os.path.join(os.path.dirname(__file__), "static"),
            template_path = os.path.join(os.path.dirname(__file__), "templates"),
            debug = tornado.options.options.debug,
        )
        tornado.web.Application.__init__(self, handlers, **settings)

class IndexHandler(tornado.web.RequestHandler):
    client = tornado.httpclient.AsyncHTTPClient()

    @asynchronous
    @gen.engine
    def get(self):
        response = yield gen.Task(self.client.fetch, "http://google.com")

        self.finish("Google's homepage is %d bytes long" % len(response.body))

class ThreadHandler(tornado.web.RequestHandler):
    @asynchronous
    def get(self):
        Worker(self.worker_done).start()

    def worker_done(self, value):
        self.finish(value)

def main():
    tornado.options.parse_command_line()
    http_server = tornado.httpserver.HTTPServer(Application())
    http_server.listen(tornado.options.options.port)
    tornado.ioloop.IOLoop.instance().start()

if __name__ == "__main__":
    main()
 18
Author: koblas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-01 07:39:13

Rozwiązanie Koblasa jest świetne. Oto alternatywa, która wykorzystuje tornado.gen

import tornado.ioloop
import tornado.web
import tornado.gen
import tornado.concurrent
import time
from threading import Thread
from functools import wraps

def run_async(func):
  @wraps(func)
  def async_func(*args, **kwargs):
    func_hl = Thread(target = func, args = args, kwargs = kwargs)
    func_hl.start()
    return func_hl

  return async_func

@run_async
def sleeper(callback):
  i = 0
  while i <= 10:
    print i
    time.sleep(1)
    i += 1
  callback('DONE')


class MainHandler(tornado.web.RequestHandler):
    @tornado.web.asynchronous
    @tornado.gen.coroutine
    def get(self):
        response = yield tornado.gen.Task(sleeper)
        self.write(response)
        self.finish()

class OtherHandler(tornado.web.RequestHandler):
    def get(self):
        self.write('hello world')
        print 'in other'
        self.finish()
 5
Author: iruvar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-11 15:15:14