Ejemplo asíncrono simple con tornado python

Quiero encontrar un ejemplo simple de servidor asíncrono. Tengo algunas funciones con mucha espera, transacciones de base de datos … etc:

def blocking_task(n): for i in xrange(n): print i sleep(1) return i 

Necesito ejecutar la función en un proceso separado sin bloquear. ¿Es posible?

Tornado está diseñado para ejecutar todas sus operaciones en un solo subproceso, pero utiliza E / S asíncrona para evitar el locking tanto como sea posible. Si la base de datos que está utilizando tiene enlaces de Python asicrónicos (idealmente los adaptados específicamente para Tornado, como Motor for MongoDB o momoko for Postgres), podrá ejecutar sus consultas de base de datos sin bloquear el servidor; No hay procesos separados o hilos necesarios.

Para abordar el ejemplo exacto que dio, donde se time.sleep(1) , puede utilizar este método para hacerlo de forma asincrónica a través de tornado coroutines:

 #!/usr/bin/python import tornado.web from tornado.ioloop import IOLoop from tornado import gen import time @gen.coroutine def async_sleep(seconds): yield gen.Task(IOLoop.instance().add_timeout, time.time() + seconds) class TestHandler(tornado.web.RequestHandler): @gen.coroutine def get(self): for i in xrange(100): print i yield async_sleep(1) self.write(str(i)) self.finish() application = tornado.web.Application([ (r"/test", TestHandler), ]) application.listen(9999) IOLoop.instance().start() 

La parte interesante es async_sleep . Ese método está creando una tarea asíncrona, que está llamando al método ioloop.add_timeout . add_timeout ejecutará una callback especificada después de un número determinado de segundos, sin bloquear el ioloop mientras espera que el tiempo de espera expire. Se espera dos argumentos:

 add_timeout(deadline, callback) # deadline is the number of seconds to wait, callback is the method to call after deadline. 

Como puede ver en el ejemplo anterior, solo proporcionamos un parámetro para add_timeout explícitamente en el código, lo que significa que terminamos esto de esta forma:

 add_timeout(time.time() + seconds, ???) 

No estamos proporcionando el parámetro de callback esperado. De hecho, cuando gen.Task ejecuta add_timeout , agrega un argumento de palabra clave de callback al final de los parámetros proporcionados explícitamente. Así que esto:

 yield gen.Task(loop.add_timeout, time.time() + seconds) 

Resultados en este ejecutándose dentro de gen.Task ():

 loop.add_timeout(time.time() + seconds, callback=gen.Callback(some_unique_key)) 

Cuando gen.Callback se ejecuta después del tiempo de espera, indica que la gen.Task ha finalizado, y la ejecución del progtwig continuará en la siguiente línea. Este flujo es un poco difícil de entender, al menos al principio (ciertamente lo fue para mí cuando lo leí por primera vez). Probablemente será útil leer la documentación del módulo de Tornado gen varias veces.

 import tornado.web from tornado.ioloop import IOLoop from tornado import gen from tornado.concurrent import run_on_executor from concurrent.futures import ThreadPoolExecutor # `pip install futures` for python2 MAX_WORKERS = 16 class TestHandler(tornado.web.RequestHandler): executor = ThreadPoolExecutor(max_workers=MAX_WORKERS) """ In below function goes your time consuming task """ @run_on_executor def background_task(self): sm = 0 for i in range(10 ** 8): sm = sm + 1 return sm @tornado.gen.coroutine def get(self): """ Request that asynchronously calls background task. """ res = yield self.background_task() self.write(str(res)) class TestHandler2(tornado.web.RequestHandler): @gen.coroutine def get(self): self.write('Response from server') self.finish() application = tornado.web.Application([ (r"/A", TestHandler), (r"/B", TestHandler2), ]) application.listen(5000) IOLoop.instance().start() 

Cuando ejecuta el código anterior, puede ejecutar una operación computacionalmente costosa en http://127.0.0.1:5000/A , que no bloquea la ejecución, consulte http://127.0.0.1:5000/B inmediatamente después de su visita http://127.0.0.1:5000/A .

Aquí actualizo la información sobre Tornado 5.0. Tornado 5.0 agrega un nuevo método IOLoop.run_in_executor . En el capítulo “Llamadas de locking de funciones” de los patrones de Coroutine :

La forma más sencilla de llamar a una función de locking desde una coroutine es usar IOLoop.run_in_executor, que devuelve futuros que son compatibles con coroutines:

@gen.coroutine def call_blocking(): yield IOLoop.current().run_in_executor(blocking_func, args)

Además, en la documentación de run_on_executor , se dice:

Este decorador no debe confundirse con el nombre similar IOLoop.run_in_executor . En general, se recomienda usar run_in_executor cuando se llama a un método de locking en lugar de usar este decorador al definir un método. Si se requiere compatibilidad con versiones anteriores de Tornado, considere la posibilidad de definir un ejecutor y usar executor.submit () en el sitio de la llamada.

En la versión 5.0, IOLoop.run_in_executor se recomienda en caso de uso de las funciones de locking de llamadas.