asyncio.assurer l'avenir vs. BaseEventLoop.créer une tâche vs. coroutine simple?
j'ai vu plusieurs tutoriels Python 3.5 de base sur asyncio faire la même opération dans différentes saveurs. Dans ce code:
import asyncio
async def doit(i):
print("Start %d" % i)
await asyncio.sleep(3)
print("End %d" % i)
return i
if __name__ == '__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)
toutes 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, avec la troisième variante, l'exécution est hors d'ordre (qui ne devrait pas compter dans la plupart des cas). Est-il d'autres différences? Y a-t-il des cas où je ne peux pas simplement utiliser la variante la plus simple (simple liste des coroutines)?
4 réponses
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 façons basées sur l'argument (y compris l'utilisation de create_task
pour les coroutines et les objets similaires à ceux du futur).
create_task
est une méthode abstraite de AbstractEventLoop
. Différentes boucles d'événements peuvent implémenter cette fonction de différentes façons.
Vous devez utiliser ensure_future
pour créer des tâches. Vous n'aurez besoin de create_task
que si vous voulez implémenter votre propre type de boucle d'événement.
Upd:
@bj0 pointait à la réponse de Guido sur ce sujet:
le point de
ensure_future()
est si vous avez quelque chose qui pourrait soit une coroutine, soit unFuture
(ce dernier comprenantTask
parce que c'est une sous-classe deFuture
), et vous voulez être en mesure d'appeler une méthode sur ce qui est défini surFuture
(probablement la seule utile exemple:cancel()
). Quand il est déjà unFuture
(ouTask
) ce ne fait rien; quand il est une coroutine il wraps il dans unTask
.Si vous savez que vous avez une coroutine et vous voulez qu'il soit prévu, la bonne API pour l'utilisation est
create_task()
. Le seul moment où vous devriez appelerensure_future()
est quand vous fournissez une API (comme la plupart de L'APIs d'asyncio) qui accepte soit une corotine soit unFuture
et vous devez faire quelque chose qui vous oblige à avoir unFuture
.
et plus tard:
en fin de compte, je crois toujours que
ensure_future()
est un nom obscur pour un morceau de fonctionnalité. Lors de la création d' une tâche d'une coroutine vous devez utiliser le nom appropriéloop.create_task()
. Peut-être qu'il devrait y avoir un pseudonyme pour ça.asyncio.create_task()
?
C'est surprenant pour moi. Ma principale motivation pour utiliser ensure_future
tout au long était que c'est fonction de niveau plus élevé par rapport au membre de loop create_task
(discussion contient quelques idées comme ajouter asyncio.spawn
ou asyncio.create_task
).
je peux également souligner que, à mon avis, il est assez commode d'utiliser une fonction universelle qui peut gérer n'importe quel Awaitable
plutôt que coroutines seulement.
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 nom approprié loop.create_task()
quand les coroutines devraient être enveloppées dans des tâches?
envelopper coroutine dans une tâche - est une façon de commencer ce coroutine "en arrière-plan". 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 __name__ == "__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 juste await long_operation()
pour sentir la différence.
create_task()
- accepte coroutines,
- les retours de la Tâche,
- elle est invoquée dans le contexte de la boucle.
ensure_future()
- accepte les contrats à Terme, des coroutines, awaitable objets,
- renvoie la tâche (ou Future si Future passe).
- si l'arg donné est un coroutine il utilise
create_task
, - l'objet boucle peut être passé.
comme vous pouvez le voir, create_task est plus spécifique.
async
fonction sans create_task ou ensure_future
Simple d'invoquer les async
fonction 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 args sont des contrats à terme, explicitement ensure_future
est redondant.
question similaire Quelle est la différence entre loop.create_task, asyncio.async / ensure_future et tâche?
pour votre exemple, les trois types s'exécutent asynchrones. la seule différence est que, dans le troisième exemple, vous avez pré-généré les 10 coroutines et les avez soumises ensemble à la boucle. donc seul le dernier donne la sortie au hasard.
Note: uniquement valable pour Python 3.7 (pour Python 3.5 se référer à la réponse précédente ).
des documents officiels:
asyncio.create_task
(ajouté en python 3.7) est la meilleure façon de créer de nouvelles tâches au lieu deensure_future()
.
détail:
ainsi, à partir de python 3.7, il existe 2 fonctions d'enrubannage de haut niveau (similaires mais différentes):
-
asyncio.create_task
: qui appellent simplementevent_loop.create_task(coro)
directement. ( voir code source ) -
ensure_future
qui appellent aussievent_loop.create_task(coro)
si elle est coroutine ou bien c'est simplement pour s'assurer que le type de retour soit un asyncio .L'avenir . ( voir code source ). Quoi qu'il en soit,Task
est toujours unFuture
en raison de son héritage de classe ( ref ).
Eh bien, utlimately ces deux fonctions wrapper vous aidera à appeler BaseEventLoop.create_task
. La seule différence est ensure_future
accepter tout awaitable
et vous aider à le transformer en un futur. Et vous pouvez également fournir votre propre paramètre event_loop
dans ensure_future
. Et selon que vous avez besoin de ces capacités ou non, vous pouvez simplement choisir quel emballage utiliser.