web-dev-qa-db-fra.com

asyncio.ensure_future vs BaseEventLoop.create_task vs simple coroutine?

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)?

78
crusaderky

Infos actuelles:

À 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).


Infos anciennes:

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 un Future (ce dernier inclut un Task parce que c'est une sous-classe de Future), et vous voulez pouvoir y appeler une méthode qui n'est définie que sur Future (probablement à propos du seul exemple utile qui soit cancel()). Quand c'est déjà un Future (ou Task), cela ne fait rien; quand c'est une coroutine c'est wraps c'est dans un Task.

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 appeler ensure_future(), c’est quand vous fournissez une API (comme la plupart des API propres d’Asyncio) qui accepte un coroutine ou un Future et vous devez faire quelque chose pour: ce qui vous oblige à avoir un Future.

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 le loop.create_task() ainsi nommé. Peut-être qu'il devrait y avoir un alias pour cette asyncio.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"

Quand les coroutines doivent-elles être emballé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 __== "__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.

93

create_task()

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

ensure_future()

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

Comme vous pouvez le constater, la tâche create_task est plus spécifique.


async fonction sans create_task ni assure_future

Un 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?

36
kwarunek

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 de ensure_future() .


Détail:

Alors maintenant, dans Python 3.7, il existe 2 fonctions d’encapsulation de premier niveau (similaires mais différentes)):

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.

11
Yeo

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.

3
ospider