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 comprenantTaskparce 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 unFutureet 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_futurequi 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,Taskest toujours unFutureen 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.