“Dispara y olvida” python async / await

A veces hay una operación asíncrona no crítica que debe ocurrir, pero no quiero esperar a que se complete. En la implementación de Tornado, puede “disparar y olvidar” una función asíncrona simplemente omitiendo la palabra clave de yield .

He estado intentando descubrir cómo “disparar y olvidar” con la nueva syntax async / await lanzada en Python 3.5. Por ejemplo, un fragmento de código simplificado:

 async def async_foo(): print("Do some stuff asynchronously here...") def bar(): async_foo() # fire and forget "async_foo()" bar() 

Sin embargo, lo que sucede es que la bar() nunca se ejecuta y, en cambio, recibimos una advertencia de tiempo de ejecución:

 RuntimeWarning: coroutine 'async_foo' was never awaited async_foo() # fire and forget "async_foo()" 

Actualizaciones:

Reemplace asyncio.ensure_future con asyncio.create_task todas partes si está usando Python> = 3.7 Es una forma más nueva y mejor de generar tareas .


asyncio.Tareas a “disparar y olvidar”

De acuerdo con los documentos de Python para asyncio.Task es posible iniciar alguna rutina para ejecutar “en segundo plano” . La tarea creada por la función asyncio.ensure_future no bloqueará la ejecución (¡por lo tanto, la función regresará inmediatamente!). Esto parece ser una forma de “disparar y olvidar” como usted solicitó.

 import asyncio async def async_foo(): print("async_foo started") await asyncio.sleep(1) print("async_foo done") async def main(): asyncio.ensure_future(async_foo()) # fire and forget async_foo() # btw, you can also create tasks inside non-async funcs print('Do some actions 1') await asyncio.sleep(1) print('Do some actions 2') await asyncio.sleep(1) print('Do some actions 3') if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) 

Salida:

 Do some actions 1 async_foo started Do some actions 2 async_foo done Do some actions 3 

¿Qué pasa si las tareas se ejecutan después de completar el bucle de eventos?

Tenga en cuenta que asyncio espera que la tarea se complete en el momento en que se complete el bucle de eventos. Así que si cambias main() a:

 async def main(): asyncio.ensure_future(async_foo()) # fire and forget print('Do some actions 1') await asyncio.sleep(0.1) print('Do some actions 2') 

Recibirás esta advertencia después de que el progtwig termine:

 Task was destroyed but it is pending! task:  

Para evitar que solo pueda esperar todas las tareas pendientes después de que se complete el ciclo de eventos:

 async def main(): asyncio.ensure_future(async_foo()) # fire and forget print('Do some actions 1') await asyncio.sleep(0.1) print('Do some actions 2') if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) # Let's also finish all running tasks: pending = asyncio.Task.all_tasks() loop.run_until_complete(asyncio.gather(*pending)) 

Matar tareas en lugar de esperarlas.

A veces no desea esperar que se realicen las tareas (por ejemplo, algunas tareas pueden crearse para que se ejecuten para siempre). En ese caso, puede simplemente cancelarlos () en lugar de esperarlos:

 import asyncio from contextlib import suppress async def echo_forever(): while True: print("echo") await asyncio.sleep(1) async def main(): asyncio.ensure_future(echo_forever()) # fire and forget print('Do some actions 1') await asyncio.sleep(1) print('Do some actions 2') await asyncio.sleep(1) print('Do some actions 3') if __name__ == '__main__': loop = asyncio.get_event_loop() loop.run_until_complete(main()) # Let's also cancel all running tasks: pending = asyncio.Task.all_tasks() for task in pending: task.cancel() # Now we should await task to execute it's cancellation. # Cancelled task raises asyncio.CancelledError that we can suppress: with suppress(asyncio.CancelledError): loop.run_until_complete(task) 

Salida:

 Do some actions 1 echo Do some actions 2 echo Do some actions 3 echo 

Esto no es una ejecución completamente asíncrona, pero tal vez run_in_executor () sea ​​adecuado para usted.

 def fire_and_forget(task, *args, **kwargs): loop = asyncio.get_event_loop() if callable(task): return loop.run_in_executor(None, task, *args, **kwargs) else: raise TypeError('Task must be a callable') def foo(): #asynchronous stuff here fire_and_forget(foo) 

Gracias Sergey por la respuesta sucinta. Aquí está la versión decorada del mismo.

 import asyncio import time def fire_and_forget(f): def wrapped(*args, **kwargs): return asyncio.get_event_loop().run_in_executor(None, f, *args, *kwargs) return wrapped @fire_and_forget def foo(): time.sleep(1) print("foo() completed") print("Hello") foo() print("I didn't wait for foo()") 

Produce

 >>> Hello >>> foo() started >>> I didn't wait for foo() >>> foo() completed