2012-10-24 15 views
11

Mit Tornado, ich habe eine Get-Anfrage, die eine lange Zeit dauert, da es viele Anfragen an einen anderen Web-Service macht und die Daten verarbeitet, könnte Minuten dauern, vollständig zu vervollständigen. Ich möchte nicht, dass dies den gesamten Webserver daran hindert, auf andere Anfragen zu antworten, was er derzeit tut.Tornado blockierende asynchrone Anfragen

Wie ich es verstehe, ist Tornado single-threaded und führt jede Anfrage synchron aus, obwohl sie asynchron behandelt (immer noch verwirrt auf diesem Bit). Es gibt Teile des langen Prozesses, die Pausenpunkte sein können, damit der Server andere Anfragen bearbeiten kann (mögliche Lösung?). Ich betreibe es auf Heroku mit einem einzigen Worker, also bin ich mir nicht sicher, wie das zu einem neuen Thread oder Multiprocessing führt, mit dem ich keine Erfahrung mit Python habe.

Hier ist was ich versuche zu tun: der Client macht den Anruf, um den Prozess zu starten, dann durchläuft ich alle 5 Sekunden einen anderen Anruf, um den Status zu überprüfen und die Seite mit neuen Informationen zu aktualisieren (lange Abfrage würde funktionieren auch, aber laufen auf dasselbe Problem hinaus). Das Problem besteht darin, dass das Starten des langen Prozesses alle neuen get-Anfragen (oder neue lange polling-Sitzungen) bis zum Abschluss blockiert.

Gibt es einen einfachen Weg, um diesen langen Anruf zu starten und ihn nicht den gesamten Webserver blockieren zu lassen? Gibt es irgendetwas, was ich in den Code schreiben kann, um zu sagen ... "Pause, gehe mit ausstehenden Anfragen und dann weiter"?

Ich muss eine Get-Anforderung auf ProcessHandler initiieren. Ich muss dann weiterhin StatusHandler abfragen können, während ProcessHandler ausgeführt wird.

Beispiel:

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() 
+0

Haben Sie das tornado.gen-Modul ausprobiert? http://www.tornadoweb.org/documentation/gen.html –

+0

erinnerst du dich, es als asynchroner Aufruf zu kommentieren: füge hinzu: @asynchronous auf deinen GET Methoden –

+0

andyboot ja, ich habe @asynchronous auf meinen GET Methoden – JeffG

Antwort

17

Hier ist eine komplette Probe Tornado-Anwendung, die das Async HTTP-Client und das gen.Task Modul verwendet, um die Dinge einfach zu machen.

Wenn Sie mehr über gen.Task in den Dokumenten lesen, werden Sie sehen, dass Sie mehrere Anfragen gleichzeitig versenden können. Dies basiert auf der Kernidee von Tornado, bei der alles nicht blockiert wird und immer noch ein einziger Prozess aufrechterhalten wird.

Update: Ich habe einen Thread-Handler hinzugefügt, um zu demonstrieren, wie Sie Arbeit in einen zweiten Thread senden und die callback() empfangen können, wenn es fertig ist.

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() 
+0

Ich habe meine Funktion in der gen.Task eingepackt aber es hat immer noch das Gleiche gemacht. Ich habe einen get erstellt, der mehrere Antworten hatte = get.Tasks(). Ich brauche sie nicht zur gleichen Zeit ausgeführt. Eigentlich müssen sie seriell sein, aber alle anderen Anfragen werden blockiert, während diese Anfrage in Bearbeitung ist. – JeffG

+0

Ich habe mein Beispiel oben aktualisiert. Ich habe versucht, alle Funktionen mit der gen.Task() zu umschließen und alles hat funktioniert, aber es hat mich immer noch daran gehindert, auf Abfragen auf StatusHandler zu antworten, bis es fertig war. – JeffG

+0

In Ihrem Beispiel ist self.function1() eine _pure_ python-Funktion, die keine anderen Aufrufe an externe Dienste ausführt?Die ursprüngliche Annahme war, dass es zu einem anderen Dienst aufgerufen hat und Sie waren daran blockiert. – koblas

5

koblas Lösung ist großartig. Hier ist eine Alternative, die tornado.gen verwendet

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() 
Verwandte Themen