J'ai vu plusieurs tutoriels de base Python 3.5 sur asyncio effectuant la même opération dans différentes versions. Dans ce code:
import asyncio
async def doit(i):
print("Start %d" % i)
await asyncio.sleep(3)
print("End %d" % i)
return i
if __== '__main__':
loop = asyncio.get_event_loop()
#futures = [asyncio.ensure_future(doit(i), loop=loop) for i in range(10)]
#futures = [loop.create_task(doit(i)) for i in range(10)]
futures = [doit(i) for i in range(10)]
result = loop.run_until_complete(asyncio.gather(*futures))
print(result)
Les trois variantes ci-dessus qui définissent la variable futures
obtiennent le même résultat; la seule différence que je peux voir est que, dans la troisième variante, l'exécution est dans le désordre (ce qui ne devrait pas avoir d'importance dans la plupart des cas). Y a-t-il une autre différence? Existe-t-il des cas où je ne peux pas simplement utiliser la variante la plus simple (liste simple de coroutines)?
À partir de Python 3,7 asyncio.create_task(coro)
) fonction de haut niveau a été ajoutée à cette fin.
Vous devriez utiliser plutôt d'autres moyens de créer des tâches à partir de plusieurs fois. Toutefois, si vous devez créer une tâche arbitraire et attendue, vous devez utiliser asyncio.ensure_future(obj)
.
ensure_future
Vs create_task
ensure_future
Est une méthode pour créer Task
à partir de coroutine
. Il crée des tâches de différentes manières en fonction d'un argument (y compris en utilisant create_task
Pour les coroutines et les objets de type futur).
create_task
Est une méthode abstraite de AbstractEventLoop
. Différentes boucles d'événements peuvent implémenter cette fonction de différentes manières.
Vous devez utiliser ensure_future
Pour créer des tâches. Vous aurez besoin de create_task
Uniquement si vous souhaitez implémenter votre propre type de boucle d'événement.
Upd:
@ bj0 pointa la réponse de Guido à ce sujet:
Le point de
ensure_future()
est si vous avez quelque chose qui pourrait être une coroutine ou unFuture
(ce dernier inclut unTask
parce que c'est une sous-classe deFuture
), et vous voulez pouvoir y appeler une méthode qui n'est définie que surFuture
(probablement à propos du seul exemple utile qui soitcancel()
). Quand c'est déjà unFuture
(ouTask
), cela ne fait rien; quand c'est une coroutine c'est wraps c'est dans unTask
.Si vous savez que vous avez une coroutine et que vous voulez la planifier, l'API à utiliser est
create_task()
. Le seul moment où vous devriez appelerensure_future()
, c’est quand vous fournissez une API (comme la plupart des API propres d’Asyncio) qui accepte un coroutine ou unFuture
et vous devez faire quelque chose pour: ce qui vous oblige à avoir unFuture
.
et ensuite:
En fin de compte, je crois toujours que
ensure_future()
est un nom assez obscur pour une fonctionnalité rarement nécessaire. Lorsque vous créez une tâche à partir d'une coroutine, vous devez utiliser leloop.create_task()
ainsi nommé. Peut-être qu'il devrait y avoir un alias pour cetteasyncio.create_task()
?
C'est surprenant pour moi. Ma principale motivation à utiliser ensure_future
A toujours été que c'était une fonction de niveau supérieur comparant au membre de la boucle create_task
(Discussion contient quelques idées comme ajouter asyncio.spawn
ou asyncio.create_task
).
Je peux aussi souligner qu’à mon avis, il est très pratique d’utiliser une fonction universelle qui peut gérer n’importe quel Awaitable
plutôt que des coroutines.
Cependant, la réponse de Guido est claire: "Lors de la création d'une tâche à partir d'une coroutine, vous devez utiliser le loop.create_task()
nommé de manière appropriée"
Envelopper la coroutine dans une tâche - est un moyen de démarrer cette coroutine "en arrière-plan". Voici un exemple:
import asyncio
async def msg(text):
await asyncio.sleep(0.1)
print(text)
async def long_operation():
print('long_operation started')
await asyncio.sleep(3)
print('long_operation finished')
async def main():
await msg('first')
# Now you want to start long_operation, but you don't want to wait it finised:
# long_operation should be started, but second msg should be printed immediately.
# Create task to do so:
task = asyncio.ensure_future(long_operation())
await msg('second')
# Now, when you want, you can await task finised:
await task
if __== "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Sortie:
first
long_operation started
second
long_operation finished
Vous pouvez remplacer asyncio.ensure_future(long_operation())
par seulement await long_operation()
pour sentir la différence.
create_task()
ensure_future()
create_task
,Comme vous pouvez le constater, la tâche create_task est plus spécifique.
async
fonction sans create_task ni assure_futureUn simple appel de la fonction async
renvoie la coroutine
>>> async def doit(i):
... await asyncio.sleep(3)
... return i
>>> doit(4)
<coroutine object doit at 0x7f91e8e80ba0>
Et depuis le gather
sous le capot assure (ensure_future
) que les arguments sont des futurs, explicitement ensure_future
est redondant.
Question similaire Quelle est la différence entre loop.create_task, asyncio.async/Ensure_future et Task?
Remarque: Valable uniquement pour Python 3.7 (pour Python 3.5, reportez-vous à réponse précédente ).
De la documentation officielle:
asyncio.create_task
(ajouté dans Python 3.7) est le moyen préférable pour générer de nouvelles tâches au lieu deensure_future()
.
Alors maintenant, dans Python 3.7, il existe 2 fonctions d’encapsulation de premier niveau (similaires mais différentes)):
asyncio.create_task
: qui appelle simplement event_loop.create_task(coro)
directement. ( voir le code source )ensure_future
qui appelle également event_loop.create_task(coro)
si c'est la coroutine ou alors c'est simplement pour s'assurer que le type de retour est un asyncio.future . ( voir le code source ). Quoi qu'il en soit, Task
est toujours un Future
en raison de son héritage de classe ( ref ).Eh bien, ces deux fonctions d’emballage vous aideront à appeler BaseEventLoop.create_task
. La seule différence est que ensure_future
Accepte n'importe quel objet awaitable
et vous aide à le convertir en avenir. Et vous pouvez également fournir votre propre paramètre event_loop
Dans ensure_future
. Et selon que vous ayez besoin de ces fonctionnalités ou non, vous pouvez simplement choisir quel wrapper utiliser.
pour votre exemple, les trois types s'exécutent de manière asynchrone. la seule différence est que, dans le troisième exemple, vous avez pré-généré toutes les 10 coroutines et les avez envoyées ensemble à la boucle. de sorte que seul le dernier donne une sortie au hasard.