¿Cómo puedo empaquetar una coroutina como función normal en un bucle de eventos?

Estoy usando asyncio para un marco de red.

En el siguiente código ( low_level es nuestra función de bajo nivel, el bloque main es nuestra entrada al progtwig, user_func es una función definida por el usuario):

 import asyncio loop = asyncio.get_event_loop() """:type :asyncio.AbstractEventLoop""" def low_level(): yield from asyncio.sleep(2) def user_func(): yield from low_level() if __name__ == '__main__': co = user_func() loop.run_until_complete(co) 

Quiero ajustar la función low_level como una función normal en lugar de una coroutine (por compatibility etc.), pero low_level está en el bucle de eventos. ¿Cómo se puede envolver como una función normal?

Debido a que low_level es una rutina, solo se puede usar ejecutando un bucle de eventos asyncio . Si desea poder llamarlo desde un código síncrono que no está ejecutando un bucle de eventos, debe proporcionar un envoltorio que realmente inicie un bucle de eventos y ejecute la rutina hasta que se complete:

 def sync_low_level(): loop = asyncio.get_event_loop() loop.run_until_complete(low_level()) 

Si desea poder llamar a low_level() desde una función que forma parte del bucle de eventos en ejecución, low_level() que se bloquee durante dos segundos, pero no tenga que usar el yield from , la respuesta es que no puede. El bucle de eventos es de un solo hilo; siempre que la ejecución esté dentro de una de sus funciones, el bucle de eventos se bloquea. No se pueden procesar otros eventos o devoluciones de llamada. Las únicas formas en que una función que se ejecuta en el bucle de eventos para devolver el control al bucle de eventos son: 1) return 2) usar el yield from . La llamada asyncio.sleep en low_level nunca podrá completarse a menos que hagas una de esas dos cosas.

Ahora, supongo que podría crear un bucle de eventos completamente nuevo , y usarlo para ejecutar el modo de espera de forma síncrona desde una rutina de rutina como parte del bucle de eventos predeterminado:

 import asyncio loop = asyncio.get_event_loop() @asyncio.coroutine def low_level(loop=None): yield from asyncio.sleep(2, loop=loop) def sync_low_level(): new_loop = asyncio.new_event_loop() new_loop.run_until_complete(low_level(loop=new_loop)) @asyncio.coroutine def user_func(): sync_low_level() if __name__ == "__main__": loop.run_until_complete(user_func()) 

Pero realmente no estoy seguro de por qué querrías hacer eso.

Si solo quiere poder hacer que low_level actúe como un método que devuelva un Future , para poder adjuntarle devoluciones de llamada, etc., simplemente envuélvalo en asyncio.async() :

 loop = asyncio.get_event_loop() def sleep_done(fut): print("Done sleeping") loop.stop() @asyncio.coroutine def low_level(loop=None): yield from asyncio.sleep(2, loop=loop) def user_func(): fut = asyncio.async(low_level()) fut.add_done_callback(sleep_done) if __name__ == "__main__": loop.call_soon(user_func) loop.run_forever() 

Salida:

 <2 second delay> "Done sleeping" 

Además, en su código de ejemplo, debe usar el decorador @asyncio.coroutine para low_level y user_func , como se indica en los documentos de asyncio :

Una coroutina es un generador que sigue ciertas convenciones. Para fines de documentación, todas las coroutinas deben estar decoradas con @ asyncio.coroutine, pero esto no puede aplicarse estrictamente.

Editar:

Así es como un usuario de un marco web síncrono puede llamar a su aplicación sin bloquear otras solicitudes:

 @asyncio.coroutine def low_level(loop=None): yield from asyncio.sleep(2, loop=loop) def thr_low_level(): loop = asyncio.new_event_loop() t = threading.Thread(target=loop.run_until_complete, args(low_level(loop=loop),)) t.start() t.join() 

Si una solicitud manejada por Flask llama a thr_low_level , se bloqueará hasta que se thr_low_level la solicitud, pero se debe liberar la GIL para todas las E / S asíncronas que low_level en low_level , permitiendo que otras solicitudes se manejen en subprocesos separados.