asyncio.ensure_future contre BaseEventLoop.create_task contre simple coroutine?

97

J'ai vu plusieurs didacticiels de base sur Python 3.5 sur asyncio effectuer 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)

Les trois variantes ci-dessus qui définissent la futuresvariable aboutissent au même résultat; la seule différence que je peux voir est qu'avec 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? Y a-t-il des cas où je ne peux pas simplement utiliser la variante la plus simple (liste simple des coroutines)?

croisé
la source

Réponses:

117

Infos réelles:

À partir de Python 3.7, asyncio.create_task(coro)une fonction de haut niveau a été ajoutée à cet effet.

Vous devriez l'utiliser à la place d'autres façons de créer des tâches à partir de coroutimes. Cependant, si vous avez besoin de créer une tâche arbitraire, vous devez utiliser asyncio.ensure_future(obj).


Anciennes infos:

ensure_future contre create_task

ensure_futureest une méthode pour créer à Taskpartir de coroutine. Il crée des tâches de différentes manières en fonction des arguments (y compris l'utilisation de create_taskpour les coroutines et les objets de type futur).

create_taskest 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_futurepour créer des tâches. Vous n'en aurez besoin create_taskque si vous allez implémenter votre propre type de boucle d'événement.

Actualiser:

@ bj0 a souligné la réponse de Guido sur ce sujet:

Le but de ensure_future()est que si vous avez quelque chose qui pourrait être une coroutine ou un Future(ce dernier inclut un Taskparce que c'est une sous-classe de Future), et que vous voulez pouvoir appeler une méthode qui n'est définie que sur Future(probablement à propos du seul exemple utile étant cancel()). Quand c'est déjà un Future(ou Task) cela ne fait rien; quand c'est une coroutine, il l' enveloppe dans un Task.

Si vous savez que vous avez une coroutine et que vous souhaitez qu'elle soit planifiée, l'API correcte à utiliser est create_task(). Le seul moment où vous devriez appeler, ensure_future()c'est lorsque vous fournissez une API (comme la plupart des API d'Asyncio) qui accepte soit une coroutine, soit une Futureet que vous devez faire quelque chose qui vous oblige à avoir un Future.

et ensuite:

En fin de compte, je crois toujours que ensure_future()c'est un nom assez obscur pour une fonctionnalité rarement nécessaire. Lors de la création d'une tâche à partir d'une coroutine, vous devez utiliser le fichier loop.create_task(). Peut-être qu'il devrait y avoir un alias pour ça asyncio.create_task()?

Cela me surprend. Ma principale motivation à utiliser depuis le ensure_futuredébut était que sa fonction de niveau supérieur était comparée au membre de la boucle create_task(la discussion contient des idées telles que l'ajout asyncio.spawnou asyncio.create_task).

Je peux également souligner qu'à mon avis, il est assez pratique d'utiliser une fonction universelle qui peut gérer n'importe quelles Awaitablecoroutines plutôt que des coroutines.

Cependant, la réponse de Guido est claire: "Lorsque vous créez une tâche à partir d'une coroutine, vous devez utiliser le nom approprié loop.create_task()"

Quand les coroutines doivent-elles être enveloppées dans des tâches?

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 __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Production:

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.

Mikhail Gerasimov
la source
3
Selon Guido, vous devriez utiliser create_tasksi vous avez vraiment besoin d'un objet de tâche, dont vous ne devriez normalement pas avoir besoin: github.com/python/asyncio/issues/477#issuecomment-268709555
bj0
@ bj0 merci pour ce lien. J'ai mis à jour la réponse en ajoutant des informations de cette discussion.
Mikhail Gerasimov
ensure_futuren'ajoute automatiquement créé Taskà la boucle d'événement?
AlQuemist
@AlQuemist chaque coroutine, future ou tâche que vous créez est automatiquement liée à une boucle d'événement, où elle sera exécutée plus tard. Par défaut, il s'agit de la boucle d'événement courante pour le thread actuel, mais vous pouvez spécifier une autre boucle d'événement à l'aide de l' loopargument mot-clé ( voir signature ensure_future ).
Mikhail Gerasimov
2
@laycat nous avons besoin à l' awaitintérieur msg()de rendre le contrôle à boucle d'événements sur le deuxième appel. Une fois la boucle d'événement reçue, le contrôle pourra démarrer long_operation(). Il a été conçu pour montrer comment ensure_futurelance la coroutine pour s'exécuter simultanément avec le flux d'exécution actuel.
Mikhail Gerasimov
45

create_task()

  • accepte les coroutines,
  • renvoie Tâche,
  • il est invoqué dans le contexte de la boucle.

ensure_future()

  • accepte les Futures, les coroutines, les objets attendus,
  • renvoie Task (ou Future si Future est passé).
  • si l'argument donné est une coroutine qu'il utilise create_task,
  • objet de boucle peut être passé.

Comme vous pouvez le voir, create_task est plus spécifique.


async fonction sans create_task ni ensure_future

La asyncfonction d' appel simple renvoie la coroutine

>>> async def doit(i):
...     await asyncio.sleep(3)
...     return i
>>> doit(4)   
<coroutine object doit at 0x7f91e8e80ba0>

Et puisque le gathersous le capot assure ( ensure_future) que les arguments sont des futurs, il ensure_futureest explicitement redondant.

Question similaire Quelle est la différence entre loop.create_task, asyncio.async / ensure_future et Task?

kwarunek
la source
13

Remarque: Uniquement valable pour Python 3.7 (pour Python 3.5, reportez-vous à la réponse précédente ).

À partir des documents officiels:

asyncio.create_task(ajouté dans Python 3.7) est le moyen préférable de générer de nouvelles tâches au lieu de ensure_future().


Détail:

Alors maintenant, à partir de Python 3.7, il existe 2 fonctions wrapper de premier niveau (similaires mais différentes):

Eh bien, ces deux fonctions wrapper vous aideront à appeler BaseEventLoop.create_task. La seule différence est d' ensure_futureaccepter n'importe quel awaitableobjet et de vous aider à le convertir en un futur. Et vous pouvez également fournir votre propre event_loopparamètre dans ensure_future. Et selon que vous avez besoin de ces fonctionnalités ou non, vous pouvez simplement choisir le wrapper à utiliser.

Yeo
la source
Je pense qu'il y a une autre différence qui n'est pas documentée: si vous essayez d'appeler asyncio.create_task avant d'exécuter la boucle, vous aurez un problème car asyncio.create_task attend une boucle en cours d'exécution. Vous pouvez cependant utiliser asyncio.ensure_future dans ce cas, car une boucle en cours d'exécution n'est pas obligatoire.
coelhudo
4

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é les 10 coroutines et soumis à la boucle ensemble. donc seul le dernier donne une sortie au hasard.

ospider
la source