web-dev-qa-db-fra.com

Comment créer et exécuter correctement des tâches simultanées à l'aide du module asyncio de python?

J'essaie de comprendre et d'implémenter correctement deux objets Task exécutés simultanément Python est relativement nouveau asyncio module.

En un mot, asyncio semble conçu pour gérer les processus asynchrones et l'exécution simultanée de Task sur une boucle d'événement. Il promeut l'utilisation de await (appliqué dans les fonctions asynchrones) comme moyen d'attendre et d'utiliser un résultat sans rappel, sans bloquer la boucle d'événement. (Les contrats à terme et les rappels restent une alternative viable.)

Il fournit également la classe asyncio.Task(), une sous-classe spécialisée de Future conçue pour envelopper des coroutines. De préférence appelé à l'aide de la méthode asyncio.ensure_future(). Les tâches asyncio sont destinées à permettre aux tâches exécutées indépendamment de s'exécuter "simultanément" avec d'autres tâches au sein de la même boucle d'événement. Je crois comprendre que Tasks sont connectés à la boucle d’événements qui continue ensuite automatiquement à diriger la coroutine entre les instructions await.

J'aime l'idée de pouvoir utiliser des tâches simultanées sans avoir à utiliser l'une des classes Executor , mais je n'ai pas trouvé beaucoup de développement pour l'implémentation.

Voici comment je le fais actuellement:

import asyncio

print('running async test')

async def say_boo():
    i = 0
    while True:
        await asyncio.sleep(0)
        print('...boo {0}'.format(i))
        i += 1

async def say_baa():
    i = 0
    while True:
        await asyncio.sleep(0)
        print('...baa {0}'.format(i))
        i += 1

# wrap in Task object
# -> automatically attaches to event loop and executes
boo = asyncio.ensure_future(say_boo())
baa = asyncio.ensure_future(say_baa())

loop = asyncio.get_event_loop()
loop.run_forever()

Dans le cas d’essayer d’exécuter simultanément deux tâches en boucle, j’ai remarqué que, à moins que cette tâche ne possède une expression interne await, elle restera bloquée dans la boucle while, bloquant ainsi les autres tâches en cours d'exécution (un peu comme une boucle normale while). Cependant, dès que les tâches doivent (a) attendre, elles semblent être exécutées simultanément sans problème.

Ainsi, les instructions await semblent fournir à la boucle d’événements un point de départ pour basculer entre les tâches, donnant ainsi l’effet de la concurrence.

Exemple de sortie avec internal await:

running async test
...boo 0
...baa 0
...boo 1
...baa 1
...boo 2
...baa 2

Exemple de sortie sans internal await:

...boo 0
...boo 1
...boo 2
...boo 3
...boo 4

Des questions

Cette implémentation passe-t-elle pour un exemple "correct" de tâches de bouclage simultané dans asyncio?

Est-il exact que la seule façon dont cela fonctionne est qu'un Task fournisse un point de blocage (expression await) afin que la boucle d'événement puisse gérer plusieurs tâches?

63
songololo

Oui, toute coroutine en cours d'exécution dans votre boucle d'événements bloque l'exécution d'autres coroutines et tâches, à moins que

  1. Appelle une autre coroutine en utilisant yield from ou await (si vous utilisez Python 3.5+).
  2. Résultats.

C'est parce que asyncio est mono-threadé; le seul moyen d'exécuter la boucle d'événement est qu'aucune autre coroutine ne soit en cours d'exécution active. En utilisant yield from/await suspend la coroutine temporairement, donnant ainsi à la boucle d'événements une chance de fonctionner.

Votre exemple de code est correct, mais dans de nombreux cas, vous ne voudriez probablement pas que le code long qui n'exécute pas les E/S asynchrones s'exécutant dans la boucle d'événements. Dans ces cas, il est souvent plus logique d'utiliser BaseEventLoop.run_in_executor pour exécuter le code dans un processus ou un fil d’arrière-plan. ProcessPoolExecutor serait le meilleur choix si votre tâche est liée au processeur, ThreadPoolExecutor serait utilisé si vous devez effectuer des E/S qui ne sont pas favorables à asyncio.

Par exemple, vos deux boucles sont complètement liées au processeur et ne partagent aucun état. La meilleure performance serait donc d'utiliser ProcessPoolExecutor pour exécuter chaque boucle en parallèle sur plusieurs processeurs:

import asyncio
from concurrent.futures import ProcessPoolExecutor

print('running async test')

def say_boo():
    i = 0
    while True:
        print('...boo {0}'.format(i))
        i += 1


def say_baa():
    i = 0
    while True:
        print('...baa {0}'.format(i))
        i += 1

if __== "__main__":
    executor = ProcessPoolExecutor(2)
    loop = asyncio.get_event_loop()
    boo = asyncio.ensure_future(loop.run_in_executor(executor, say_boo))
    baa = asyncio.ensure_future(loop.run_in_executor(executor, say_baa))

    loop.run_forever()
73
dano

Vous n'avez pas nécessairement besoin d'un yield from x Pour donner le contrôle à la boucle d'événement.

Dans votre exemple, je pense que la méthode appropriée consisterait à faire un yield None Ou, de manière équivalente, un simple yield, plutôt qu'un yield from asyncio.sleep(0.001):

import asyncio

@asyncio.coroutine
def say_boo():
  i = 0
  while True:
    yield None
    print("...boo {0}".format(i))
    i += 1

@asyncio.coroutine
def say_baa():
  i = 0
  while True:
    yield
    print("...baa {0}".format(i))
    i += 1

boo_task = asyncio.async(say_boo())
baa_task = asyncio.async(say_baa())

loop = asyncio.get_event_loop()
loop.run_forever()

Les coroutines sont simplement d'anciens générateurs Python. En interne, la boucle d'événements asyncio conserve un enregistrement de ces générateurs et appelle gen.send() sur chacun d'eux, l'un après l'autre. dans une boucle sans fin. Chaque fois que vous yield, l'appel de gen.send() est terminé et la boucle peut continuer. (Je simplifie la procédure; jetez un coup d'œil autour de https: //hg.python.org/cpython/file/3.4/Lib/asyncio/tasks.py#l265 pour le code actuel)

Cela dit, je choisirais toujours la voie run_in_executor Si vous devez effectuer des calculs gourmands en ressources CPU sans partage de données.

13
Jashandeep Sohi