web-dev-qa-db-fra.com

Lorsque vous utilisez asyncio, comment autoriser toutes les tâches en cours à se terminer avant de fermer la boucle

J'ai le code suivant:

@asyncio.coroutine
def do_something_periodically():
    while True:
        asyncio.async(my_expensive_operation())
        yield from asyncio.sleep(my_interval)
        if shutdown_flag_is_set:
            print("Shutting down")
            break

Je lance cette fonction jusqu'à la fin. Le problème se produit lorsque l'arrêt est défini - la fonction est terminée et les tâches en attente ne sont jamais exécutées. (Vous voyez ceci comme une erreur

task: <Task pending coro=<report() running at script.py:33> wait_for=<Future pending cb=[Task._wakeup()]>>

). Comment planifier un arrêt correctement?

Pour donner un peu de contexte, j'écris un moniteur système qui lit dans/proc/stat toutes les 5 secondes, calcule l'utilisation du processeur dans cette période, puis envoie le résultat à un serveur. Je souhaite continuer à planifier ces tâches de surveillance jusqu'à ce que je reçoive sigterm, lorsque j'arrête de planifier, j'attends la fin de toutes les tâches en cours et je quitte correctement.

39
derekdreery

Vous pouvez récupérer des tâches non terminées et réexécuter la boucle jusqu'à la fin, puis fermer la boucle ou quitter votre programme.

pending = asyncio.Task.all_tasks()
loop.run_until_complete(asyncio.gather(*pending))
  • en attente est une liste de tâches en attente.
  • asyncio.gather () permet d’attendre plusieurs tâches à la fois.

Si vous voulez vous assurer que toutes les tâches sont accomplies dans une coroutine (vous avez peut-être une coroutine "principale"), vous pouvez le faire de cette façon, par exemple

@asyncio.coroutine
def do_something_periodically():
    while True:
        asyncio.async(my_expensive_operation())
        yield from asyncio.sleep(my_interval)
        if shutdown_flag_is_set:
            print("Shutting down")
            break

    yield from asyncio.gather(*asyncio.Task.all_tasks())

De plus, dans ce cas, puisque toutes les tâches sont créées dans le même répertoire, vous avez déjà accès aux tâches:

@asyncio.coroutine
def do_something_periodically():
    tasks = []
    while True:
        tasks.append(asyncio.async(my_expensive_operation()))
        yield from asyncio.sleep(my_interval)
        if shutdown_flag_is_set:
            print("Shutting down")
            break

    yield from asyncio.gather(*tasks)
39
Martin Richard

Depuis Python 3.7, la réponse ci-dessus utilise plusieurs API obsolètes ((asyncio.async et Task.all_tasks, @ asyncio.coroutine, rendement de, etc.) et vous devriez plutôt utiliser ceci:

import asyncio


async def my_expensive_operation(expense):
    print(await asyncio.sleep(expense, result="Expensive operation finished."))


async def do_something_periodically(expense, interval):
    while True:
        asyncio.create_task(my_expensive_operation(expense))
        await asyncio.sleep(interval)


loop = asyncio.get_event_loop()
coro = do_something_periodically(1, 1)

try:
    loop.run_until_complete(coro)
except KeyboardInterrupt:
    coro.close()
    tasks = asyncio.all_tasks(loop)
    expensive_tasks = {task for task in tasks if task._coro.__!= coro.__name__}
    loop.run_until_complete(asyncio.gather(*expensive_tasks))
6

Vous pouvez également envisager d'utiliser asyncio.shield , bien qu'en procédant ainsi, vous n'obtiendrez pas ALL les tâches en cours terminées, mais seulement blindé. Mais cela peut toujours être utile dans certains scénarios.

De plus, à partir de Python 3.7, nous pouvons également utiliser la méthode API de haut niveau asynio.run here. En tant que développeur Python, Yury Selivanov suggère: https://youtu.be/ReXxO_azV-w?t=636
Remarque: La fonction asyncio.run a été ajoutée à asyncio dans Python 3.7 à titre provisoire.

J'espère que cela pourra aider!

import asyncio


async def my_expensive_operation(expense):
    print(await asyncio.sleep(expense, result="Expensive operation finished."))


async def do_something_periodically(expense, interval):
    while True:
        asyncio.create_task(my_expensive_operation(expense))
        # using asyncio.shield
        await asyncio.shield(asyncio.sleep(interval))


coro = do_something_periodically(1, 1)

if __== "__main__":
    try:
        # using asyncio.run
        asyncio.run(coro)
    except KeyboardInterrupt:
        print('Cancelled!')
0
Ramil Aglyautdinov