2012-01-11 4 views
16

Tengo una función de generador de pitón que produce fragmentos de texto. Me gustaría escribir un método get para una subclase tornado.web.RequestHandler que iterará sobre el generador, escribiendo los fragmentos en la respuesta a medida que avanza.Usando un generador de python simple como una rutina en un controlador asincrónico Tornado?

Dado que se trata de Tornado, y dado que el generador puede tardar más de un segundo en procesarse, pensé que sería bueno hacer el controlador asíncrono, usar este generador como una rutina paralela y pasar el control al IOLoop después de cada pedazo. Sin embargo, no puedo entender ni explicar cómo hacerlo.

Aquí está mi ejemplo (bloqueo) Código:

class TextHandler(web.RequestHandler): 
    @web.asynchronous 
    def get(self, n): 
     generator = self.generate_text(100000) 
     # Clearly, this will block. How to make it asynchronous? 
     for text in generator: 
      self.write(text) 

    def generate_text(n): 
     for x in xrange(n): 
      if not x % 15: 
       yield "FizzBuzz\n" 
      elif not x % 5: 
       yield "Buzz\n" 
      elif not x % 3: 
       yield "Fizz\n" 
      else: 
       yield "%s\n" % x 

¿Cómo puedo hacer que esto funcione manejador de forma asíncrona?

+0

En realidad no es claro lo que se va a lograr. ¿Desea dejar get() antes de que se hayan iterado todos los valores del generador y que vuelva cuando los nuevos valores estén listos? Si es así, entonces no puedes hacer eso. En esta función en particular, su código tiene un solo hilo y, si lo abandona, pierde el contexto. Por otro lado, el método marcado como asíncrono, que generalmente implica que el controlador se llama formar un grupo de subprocesos, entonces, debería estar bien bloquearlo allí. – real4x

+0

Mientras exista el generador, tiene todo el contexto que necesito. Esa es la belleza de los generadores: co-rutinas en un solo hilo. Por supuesto, usted tiene que manejar la programación usted mismo, que es quizás el problema real aquí. –

Respuesta

16

Aquí hay una versión básica de lo que está describiendo. Para evitar el bloqueo, puede pasar su generador al IOLoop a través de una función de devolución de llamada. El truco aquí es que no está usando un proceso que hace IO real y por lo tanto no tiene un controlador de proceso/archivo de nivel de sistema operativo para agregarlo al IOLoop a través del add_handler, sino que puede usar una llamada add_callback y llamarlo repetidamente desde la función de devolución de llamada para mantener la función en la cola de devolución de llamada de IOLoop hasta que el generador haya terminado.

import tornado.httpserver 
import tornado.ioloop 
import tornado.web 

class TextHandler(tornado.web.RequestHandler): 
    @tornado.web.asynchronous 
    def get(self): 
     self.generator = self.generate_text(1000) 
     tornado.ioloop.IOLoop.instance().add_callback(self.loop) 

    def loop(self): 
     try: 
      text = self.generator.next() 
      self.write(text) 
      tornado.ioloop.IOLoop.instance().add_callback(self.loop) 
     except StopIteration: 
      self.finish() 

    def generate_text(self, n): 
     for x in xrange(n): 
      if not x % 15: 
       yield "FizzBuzz\n" 
      elif not x % 5: 
       yield "Buzz\n" 
      elif not x % 3: 
       yield "Fizz\n" 
      else: 
       yield "%s\n" % x 

application = tornado.web.Application([ 
    (r"/text/", TextHandler), 
]) 

http_server = tornado.httpserver.HTTPServer(application) 
http_server.listen(8888) 
tornado.ioloop.IOLoop.instance().start() 
+0

Por qué sí, parece exactamente lo que quiero. No había pensado en programar el ciclo en sí mismo como una devolución de llamada. –

+1

@philofinfinitejest Observación menor, es mejor utilizar IOLoop.current() en lugar de IOLoop.instance(). En mi caso, eso fue crusial. También es recomendado por [docs] (http://tornado.readthedocs.org/en/latest/ioloop.html?highlight=ioloop#tornado.ioloop.IOLoop.current) – prokher

14

También es posible utilizar la nueva interfaz tornado's gen a los procesos asincrónicos:

import tornado.httpserver 
import tornado.ioloop 
import tornado.web 
import tornado.gen 

class TextHandler(tornado.web.RequestHandler): 

    @tornado.web.asynchronous 
    @tornado.gen.engine 
    def get(self): 

     def cb(it, callback): 
      try: 
       value = it.next() 
      except StopIteration: 
       value = None 
      callback(value) 

     it = self.generate_text(1000) 
     while True: 
      response = yield tornado.gen.Task(cb, it) 
      if response: 
       self.write(response) 
      else: 
       break 
     self.finish() 

    def generate_text(self, n): 
     for x in xrange(n): 
      if not x % 15: 
       yield "FizzBuzz\n" 
      elif not x % 5: 
       yield "Buzz\n" 
      elif not x % 3: 
       yield "Fizz\n" 
      else: 
       yield "%s\n" % x 

application = tornado.web.Application([ 
    (r"/text/", TextHandler), 
]) 

http_server = tornado.httpserver.HTTPServer(application) 
http_server.listen(8888) 
tornado.ioloop.IOLoop.instance().start() 
+0

Creo que veo lo que sucede allí, pero el flujo de control es más misterioso (sin tener una comprensión profunda de lo que gen.Task hace entre bastidores). El uso de @cptphil de las devoluciones programadas es mucho más sencillo. –

+0

Además, podría ser mejor usar 'si la respuesta no es None' en lugar de' if response', en caso de que usemos un generador que arroje cadenas vacías. El ejemplo no lo hará, pero mi caso de uso real lo hará. :) –

+1

+1 no tenía conocimiento de tornado.gen – philofinfinitejest

Cuestiones relacionadas