Parfois, une opération asynchrone non critique doit être exécutée, mais je ne veux pas attendre qu'elle se termine. Dans l'implémentation en coroutine de Tornado, vous pouvez "déclencher et oublier" une fonction asynchrone en omettant simplement le mot clé yield
.
J'ai essayé de comprendre comment "déclencher et oublier" avec la nouvelle syntaxe async
/await
publiée dans Python 3.5. Par exemple, une version simplifiée extrait de code:
async def async_foo():
print("Do some stuff asynchronously here...")
def bar():
async_foo() # fire and forget "async_foo()"
bar()
Cependant, ce qui se passe, c’est que bar()
ne s’exécute jamais et nous obtenons à la place un avertissement à l’exécution:
RuntimeWarning: coroutine 'async_foo' was never awaited
async_foo() # fire and forget "async_foo()"
Upd:
Remplacez asyncio.ensure_future
Par asyncio.create_task
Partout si vous utilisez Python> = 3.7. C'est plus récent et plus sympa pour générer une tâche .
Selon python docs pour asyncio.Task
il est possible de démarrer un coroutine pour exécuter "en arrière-plan" . La tâche créée par asyncio.ensure_future
fonction ne bloquera pas l'exécution (donc la fonction retournera immédiatement!). Cela ressemble à un moyen de “ feu et oublie "comme tu l'as demandé.
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 __== '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Sortie:
Do some actions 1
async_foo started
Do some actions 2
async_foo done
Do some actions 3
Notez que asyncio s'attend à ce que la tâche soit terminée au moment où la boucle d'événement est terminée. Donc, si vous changez main()
en:
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')
Vous recevrez cet avertissement une fois le programme terminé:
Task was destroyed but it is pending!
task: <Task pending coro=<async_foo() running at [...]
Pour éviter cela, vous pouvez simplement attendre toutes les tâches en attente après la fin de la boucle d'événement:
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 __== '__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))
Parfois, vous ne voulez pas attendre que des tâches soient effectuées (par exemple, certaines tâches peuvent être créées pour être exécutées indéfiniment). Dans ce cas, vous pouvez simplement les annuler () au lieu de les attendre:
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 __== '__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)
Sortie:
Do some actions 1
echo
Do some actions 2
echo
Do some actions 3
echo
Ce n’est pas une exécution entièrement asynchrone, mais peut-être que run_in_executor () vous convient.
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)
Merci Sergey pour la réponse succincte. Voici la version décorée de la même chose.
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()")
Produit
>>> Hello
>>> foo() started
>>> I didn't wait for foo()
>>> foo() completed