2012-10-24 5 views
11

Uso Tornado, ho una richiesta GET che richiede molto tempo in quanto rende molte richieste a un altro servizio web ed elabora i dati, potrebbe richiedere alcuni minuti per completare pienamente. Non voglio che questo blocchi l'intero server web dal rispondere ad altre richieste, che attualmente fa.Tornado blocco richieste asincrone

Da quanto ho capito, Tornado è a singolo thread ed esegue ogni richiesta in modo sincrono, anche se li gestisce in modo asincrono (ancora confuso su quel po '). Ci sono parti del processo lungo che potrebbero essere punti di pausa per consentire al server di gestire altre richieste (possibile soluzione?). Lo sto eseguendo su Heroku con un solo operatore, quindi non sono sicuro di come questo si traduca nella generazione di un nuovo thread o multiprocessing, che non ho esperienza con Python.

Qui è quello che sto cercando di fare: il client effettua la chiamata ottenere per avviare il processo, poi ho ciclo attraverso un altro ottenere chiamare ogni 5 secondi per verificare lo stato e aggiornare la pagina con le nuove informazioni (polling lungo sarebbe funziona anche se si trova nello stesso problema). Il problema è che l'avvio del processo lungo blocca tutte le nuove richieste di acquisizione (o nuove sessioni di polling lunghe) fino al completamento.

C'è un modo semplice per dare il via questo arrivare a lungo chiamare e non averlo bloccare l'intero server Web nel processo? C'è qualcosa che posso inserire nel codice per dire ... "pausa, vai a gestire le richieste in sospeso e poi continua"?

Ho bisogno di avviare una richiesta GET sulla ProcessHandler. Devo quindi continuare a essere in grado di interrogare StatusHandler mentre ProcessHandler è in esecuzione.

Esempio:

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

Hai provato il modulo tornado.gen? http://www.tornadoweb.org/documentation/gen.html –

+0

ti sei ricordato di annotare come una chiamata asincrona: aggiungi: @asynchronous sui tuoi metodi GET –

+0

eyboot sì, ho @asynchronous sui miei metodi GET – JeffG

risposta

17

Ecco un Tornado un'applicazione di esempio completo che utilizza il client HTTP asincrone e il modulo gen.Task per rendere le cose semplici.

Se leggi ulteriori informazioni su gen.Task nella documentazione, vedrai che puoi effettivamente inviare più richieste contemporaneamente. Questo sta utilizzando l'idea centrale di Tornado in cui tutto non è un blocco e continua a mantenere un singolo processo.

Aggiornamento: Ho aggiunto un gestore di thread per dimostrare come è possibile inviare il lavoro in un secondo thread e ricevere il callback() al termine.

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

Ho avvolto la mia funzione nel gen.Task ma ha comunque fatto la stessa cosa. Ho creato un get con risposta multipla = get.Tasks(). Non ho bisogno che vengano eseguiti nello stesso momento .. in effetti devono essere di serie, ma qualsiasi altra richiesta di richiesta viene bloccata mentre questa è in corso. – JeffG

+0

Ho aggiornato il mio esempio sopra. Ho cercato di avvolgere tutte le funzioni con il gen.Task() e tutto ha funzionato, ma ancora mi ha bloccato dal fare rispondendo alle domande su statusHandler fino a quando fu terminata. – JeffG

+0

Nel tuo esempio self.function1() è una funzione python _pure che non fa altre chiamate a servizi esterni?L'ipotesi originale era che chiamasse su un altro servizio e il tuo fosse bloccato su quello. – koblas

5

La soluzione dei koblas è eccezionale. Ecco un'alternativa che fa uso di 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()