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

56
demandé sur crusaderky 2016-03-31 23:15:20

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 un Future (ce dernier comprenant Task parce que c'est une sous-classe de Future ), et vous voulez être en mesure d'appeler une méthode sur ce qui est défini sur Future (probablement la seule utile exemple: cancel() ). Quand il est déjà un Future (ou Task ) ce ne fait rien; quand il est une coroutine il wraps il dans un Task .

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 appeler ensure_future() est quand vous fournissez une API (comme la plupart de L'APIs d'asyncio) qui accepte soit une corotine soit un Future et vous devez faire quelque chose qui vous oblige à avoir un Future .

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.

73
répondu Mikhail Gerasimov 2018-06-20 19:56:09

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?

28
répondu kwarunek 2018-03-24 10:09:22

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.

2
répondu ospider 2017-06-18 10:52:17

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


détail:

ainsi, à partir de python 3.7, il existe 2 fonctions d'enrubannage de haut niveau (similaires mais différentes):

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.

2
répondu Yeo 2018-08-26 14:21:08