run - Python "dispara y olvida" asíncrono/espera
process async python (3)
A veces hay que realizar una operación asincrónica no crítica, pero no quiero esperar a que se complete.
En la implementación de rutina de Tornado puede "disparar y olvidar" una función asincrónica simplemente omitiendo la palabra clave de
yield
.
He estado tratando de descubrir cómo "disparar y olvidar" con la nueva sintaxis
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
bar()
nunca se ejecuta y en su lugar recibimos una advertencia de tiempo de ejecución:
RuntimeWarning: coroutine ''async_foo'' was never awaited
async_foo() # fire and forget "async_foo()"
Esta no es una ejecución completamente asíncrona, pero quizás 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 sucinta respuesta. Aquí está la versión decorada de la misma.
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
Upd:
Reemplace
asyncio.ensure_future
con
asyncio.create_task
todas partes si está usando Python> = 3.7 Es la forma más nueva y mejor
de generar tareas
.
asyncio.Task para "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
function
asyncio.ensure_future
no bloqueará la ejecución (¡por lo tanto, la función volverá 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())
Salida:
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 complete 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.
Entonces, si va a cambiar
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á esta advertencia una vez que finalice el programa:
Task was destroyed but it is pending!
task: <Task pending coro=<async_foo() running at [...]
Para evitar que 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))
Mata tareas en lugar de esperarlas
A veces no desea esperar que se realicen las tareas (por ejemplo, algunas tareas pueden crearse para ejecutarse 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)
Salida:
Do some actions 1
echo
Do some actions 2
echo
Do some actions 3
echo