Solución:
Upd:
Reemplazar asyncio.ensure_future
con asyncio.create_task
en todas partes si está usando Python> = 3.7 Es una forma más nueva y agradable de generar tareas.
asyncio.Tarea de “disparar y olvidar”
Según los documentos de Python para asyncio.Task
es posible iniciar alguna corrutina para ejecutar “en segundo plano”. La tarea creada por asyncio.ensure_future
la función no bloqueará la ejecución (por lo tanto, la función regresará inmediatamente). Esto parece una forma de “disparar y olvidar” como lo 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())
Producción:
Do some actions 1
async_foo started
Do some actions 2
async_foo done
Do some actions 3
¿Qué sucede si las tareas se ejecutan después de que se completa el ciclo de eventos?
Tenga en cuenta que asyncio espera que la tarea se complete en el momento en que se complete el ciclo de eventos. Así que si cambias main()
para:
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á esta advertencia después de que finalice el programa:
Task was destroyed but it is pending!
task: <Task pending coro=<async_foo() running at [...]
Para evitarlo, puede 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))
Mata tareas en lugar de esperarlas
A veces, no desea esperar a que se realicen tareas (por ejemplo, algunas tareas pueden crearse para que se ejecuten para siempre). En ese caso, puede 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)
Producción:
Do some actions 1
echo
Do some actions 2
echo
Do some actions 3
echo
Gracias Sergey por la sucinta respuesta. 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
Nota: Verifique mi otra respuesta que hace lo mismo usando hilos simples.
Esta no es una ejecución completamente asincrónica, 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)