web-dev-qa-db-fra.com

comment ajouter une coroutine à une boucle asyncio en cours d'exécution?

Comment ajouter une nouvelle coroutine à une boucle asyncio en cours d'exécution? C'est à dire. celui qui exécute déjà un ensemble de coroutines.

Comme solution de contournement, on peut attendre que les coroutines existantes soient complètes, puis initialiser une nouvelle boucle (avec la coroutine supplémentaire). Mais y a-t-il une meilleure façon?

18
Petri

Vous pouvez utiliser create_task pour planifier de nouvelles coroutines:

import asyncio

async def cor1():
    ...

async def cor2():
    ...

async def main(loop):
    await asyncio.sleep(0)
    t1 = loop.create_task(cor1())
    await cor2()
    await t1

loop = asyncio.get_event_loop()
loop.run_until_complete(main(loop))
loop.close()
13
Jashandeep Sohi

Votre question est très proche de "Comment ajouter un appel de fonction au programme en cours?"

Quand exactement devez-vous ajouter un nouveau coroutine à la boucle d'événement?

Voyons quelques exemples. Voici le programme qui démarre la boucle d’événement avec deux coroutines en parallèle:

import asyncio
from random import randint


async def coro1():
    res = randint(0,3)
    await asyncio.sleep(res)
    print('coro1 finished with output {}'.format(res))
    return res

async def main():
    await asyncio.gather(
        coro1(),
        coro1()
    ) # here we have two coroutines running parallely

if __== "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Sortie:

coro1 finished with output 1
coro1 finished with output 2
[Finished in 2.2s]

Peut-être devrez-vous ajouter quelques coroutines qui prendraient les résultats de coro1 et les utiliser dès qu’il serait prêt? Dans ce cas, créez simplement une coroutine qui attend coro1 et utilisez sa valeur de retour:

import asyncio
from random import randint


async def coro1():
    res = randint(0,3)
    await asyncio.sleep(res)
    print('coro1 finished with output {}'.format(res))
    return res

async def coro2():
    res = await coro1()
    res = res * res
    await asyncio.sleep(res)
    print('coro2 finished with output {}'.format(res))
    return res

async def main():
    await asyncio.gather(
        coro2(),
        coro2()
    ) # here we have two coroutines running parallely

if __== "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

Sortie:

coro1 finished with output 1
coro2 finished with output 1
coro1 finished with output 3
coro2 finished with output 9
[Finished in 12.2s]

Pensez aux coroutines comme aux fonctions régulières avec une syntaxe spécifique. Vous pouvez démarrer un ensemble de fonctions à exécuter en parallèle (avec asyncio.gather), vous pouvez lancer la fonction suivante après la première utilisation, vous pouvez créer de nouvelles fonctions qui appellent d’autres.

8
Mikhail Gerasimov

Aucune des réponses ici ne semble répondre exactement à la question. Il est possible d’ajouter des tâches à une boucle d’événement en cours d’exécution en faisant effectuer une tâche "parent" à votre place. Je ne sais pas quel est le moyen le plus pythonique de s’assurer que ce parent ne se termine pas tant que ses enfants n’ont pas tous fini (en supposant que ce soit le comportement souhaité), mais cela fonctionne.

import asyncio
import random


async def add_event(n):
    print('starting ' + str(n))
    await asyncio.sleep(n)
    print('ending ' + str(n))
    return n


async def main(loop):

    added_tasks = []

    delays = [x for x in range(5)]

    # shuffle to simulate unknown run times
    random.shuffle(delays)

    for n in delays:
        print('adding ' + str(n))
        task = loop.create_task(add_event(n))
        added_tasks.append(task)
        await asyncio.sleep(0)

    print('done adding tasks')

    # make a list of tasks that (maybe) haven't completed
    running_tasks = added_tasks[::]

    # wait until we see that all tasks have completed
    while running_tasks:
        running_tasks = [x for x in running_tasks if not x.done()]
        await asyncio.sleep(0)

    print('done running tasks')

    # extract the results from the tasks and return them
    results = [x.result() for x in added_tasks]
    return results


loop = asyncio.get_event_loop()
results = loop.run_until_complete(main(loop))
loop.close()
print(results)
0
Adam