Maison  >  Article  >  développement back-end  >  Créer et gérer des tâches avec Asyncio

Créer et gérer des tâches avec Asyncio

王林
王林original
2024-08-15 06:37:08573parcourir

Creating and Managing Tasks with Asyncio

Asyncio permet aux développeurs d'écrire des programmes asynchrones en Python sans tracas. Le module propose également de nombreuses façons de réaliser des tâches asynchrones et avec la multitude de façons de le faire, il peut devenir difficile de savoir laquelle utiliser.

Dans cet article, nous aborderons les nombreuses façons dont vous pouvez créer et gérer des tâches avec asyncio.

Qu'est-ce qu'une tâche asyncio ?

Dans asyncio, une tâche est un objet qui encapsule une coroutine et la planifie pour qu'elle s'exécute dans la boucle d'événements. En termes simples, une tâche est un moyen d'exécuter une coroutine simultanément avec d'autres tâches. Une fois qu'une tâche est créée, la boucle d'événements l'exécute, la met en pause et la reprend si nécessaire pour permettre à d'autres tâches de s'exécuter.

Méthodes de création et de gestion des tâches Asyncio

Maintenant, nous pouvons discuter des méthodes de création et de gestion des tâches. Tout d'abord, pour créer une tâche en Python à l'aide d'asyncio, vous utilisez la méthode asyncio.create_task qui prend les arguments suivants :

  • coro (obligatoire) : L'objet coroutine à planifier. C'est la fonction que vous souhaitez exécuter de manière asynchrone.

  • nom (facultatif) : un nom pour la tâche qui peut être utile à des fins de débogage ou de journalisation. Vous pouvez attribuer une chaîne à ce paramètre.

    • Vous pouvez également définir ou obtenir le nom plus tard en utilisant Task.set_name(name) et Task.get_name().
  • contexte (facultatif) : introduit dans Python 3.11, ceci est utilisé pour définir une variable de contexte pour la tâche, permettant le stockage local de la tâche. C'est similaire au stockage local des threads mais pour les tâches asyncio.

    • Cet argument n'est pas couramment utilisé, sauf si vous avez affaire à des scénarios avancés qui nécessitent une gestion du contexte.

Voici un exemple d'utilisation de asyncio.create_task :

import asyncio

# Define a coroutine
async def greet(name):
    await asyncio.sleep(1)  # Simulate an I/O-bound operation
    print(f"Hello, {name}!")

async def main():
    # Create tasks
    task1 = asyncio.create_task(greet("Alice"), name="GreetingAlice")
    task2 = asyncio.create_task(greet("Bob"), name="GreetingBob")

    # Check task names
    print(f"Task 1 name: {task1.get_name()}")
    print(f"Task 2 name: {task2.get_name()}")

    # Wait for both tasks to complete
    await task1
    await task2

# Run the main function
asyncio.run(main())

Lorsque vous créez une tâche, vous pouvez exécuter de nombreuses méthodes telles que :

  • .cancel() : pour annuler la tâche.

  • .add_done_callback(cb) : pour ajouter une fonction de rappel qui s'exécute lorsque la tâche est terminée.

  • .done() : pour vérifier si la tâche est terminée.

  • .result() : pour récupérer le résultat de la tâche une fois celle-ci terminée.

Maintenant que nous comprenons comment créer une tâche, voyons comment gérer l'attente d'une tâche ou d'une multitude de tâches.

En attente de la fin des tâches

Dans cette section, nous verrons comment attendre la fin d'une tâche, pour une ou plusieurs tâches. La programmation asynchrone est basée sur le fait que nous pouvons continuer l'exécution d'un programme si nous avons une tâche asynchrone en cours d'exécution. Il peut y avoir des moments où vous souhaitez mieux contrôler le flux et vous assurer d'avoir un résultat avec lequel vous pouvez travailler avant de poursuivre l'exécution du programme en toute sécurité.

Pour attendre la fin d'une seule tâche, vous pouvez utiliser asyncio.wait_for. Il faut deux arguments :

  • awaitable (obligatoire) : il s'agit de la coroutine, de la tâche ou du futur que vous souhaitez attendre. Il peut s'agir de n'importe quel objet pouvant être attendu, comme un appel de fonction coroutine, un asyncio.Task ou un asyncio.Future.

  • timeout (facultatif) : ceci spécifie le nombre maximum de secondes à attendre pour que l'aw se termine. Si le délai d'attente est atteint et que l'attente n'est pas terminée, asyncio.wait_for déclenche une TimeoutError. Si le délai d'attente est défini sur Aucun, la fonction attendra indéfiniment la fin de l'attente.

Voici un exemple où cette méthode est utilisée :

import asyncio

async def slow_task():
    print("Task started...")
    await asyncio.sleep(5)  # Simulating a long-running task
    print("Task finished!")
    return "Completed"

async def main():
    try:
        # Wait for slow_task to finish within 2 seconds
        result = await asyncio.wait_for(slow_task(), timeout=2)
        print(result)
    except asyncio.TimeoutError:
        print("The task took too long and was canceled!")

asyncio.run(main())

Dans le code ci-dessus, slow_task() est une coroutine qui simule une tâche de longue durée en dormant pendant 5 secondes. La ligne asyncio.wait_for(slow_task(), timeout=2) attend la fin de la tâche mais limite l'attente à 2 secondes, provoquant un timeout car la tâche prend plus de temps. Lorsque le délai d'attente est dépassé, une TimeoutError est déclenchée, la tâche est annulée et l'exception est gérée en imprimant un message indiquant que la tâche a pris trop de temps.

Nous pouvons également attendre que plusieurs ou un groupe de tâches soient terminés. Ceci est possible en utilisant asyncio.wait, asyncio.gather ou asyncio.as_completed. Explorons chaque méthode.

asyncio.attendre

La méthode asyncio.wait attend une collection de tâches et renvoie deux ensembles : un pour les tâches terminées et un pour les tâches en attente. Il prend les arguments suivants :

  • aws (obligatoire, itérable ou attendu) : une collection d'objets, de tâches ou de futurs coroutines que vous souhaitez attendre.

  • timeout (float ou None, facultatif) : le nombre maximum de secondes à attendre. S'il n'est pas fourni, il attend indéfiniment.

  • return_when (constant, facultatif) : Spécifie quand asyncio.wait doit revenir. Les options incluent :

    • asyncio.ALL_COMPLETED (default): Returns when all tasks are complete.
    • asyncio.FIRST_COMPLETED: Returns when the first task is completed.
    • asyncio.FIRST_EXCEPTION: Returns when the first task raises an exception.

Let's see how it is used in an example.

import asyncio
import random

async def task():
    await asyncio.sleep(random.uniform(1, 3))

async def main():
    tasks = [asyncio.create_task(task()) for _ in range(3)]
    done, pending = await asyncio.wait(tasks, return_when=asyncio.FIRST_COMPLETED)
    print(f"Done tasks: {len(done)}, Pending tasks: {len(pending)}")

asyncio.run(main())

In the code above, asyncio.wait waits for a group of tasks and returns two sets: one with completed tasks and another with those still pending. You can control when it returns, such as after the first task is completed or after all tasks are done. In the example, asyncio.wait returns when the first task is completed, leaving the rest in the pending set.

asyncio.gather

The asyncio.gather method runs multiple awaitable objects concurrently and returns a list of their results, optionally handling exceptions. Let's see the arguments it takes.

  • *aws (required, multiple awaitables): A variable number of awaitable objects (like coroutines, tasks, or futures) to run concurrently.

  • return_exceptions (bool, optional): If True, exceptions in the tasks will be returned as part of the results list instead of being raised.

Let's see how it can be used in an example.

import asyncio
import random

async def task(id):
    await asyncio.sleep(random.uniform(1, 3))
    return f"Task {id} done"

async def main():
    results = await asyncio.gather(task(1), task(2), task(3))
    print(results)

asyncio.run(main())

In the code above, asyncio.gather runs multiple awaitable objects concurrently and returns a list of their results in the order they were passed in. It allows you to handle exceptions gracefully if return_exceptions is set to True. In the example, three tasks are run simultaneously, and their results are returned in a list once all tasks are complete.

asyncio.as_completed

The asyncio.as_completed method is used to return an iterator that yields tasks as they are completed, allowing results to be processed immediately. It takes the following arguments:

  • aws (iterable of awaitables): A collection of coroutine objects, tasks, or futures.

  • timeout (float or None, optional): The maximum number of seconds to wait for tasks to complete. If not provided, it waits indefinitely.

Example

import asyncio
import random

async def task(id):
    await asyncio.sleep(random.uniform(1, 3))
    return f"Task {id} done"

async def main():
    tasks = [task(i) for i in range(3)]
    for coro in asyncio.as_completed(tasks):
        result = await coro
        print(result)

asyncio.run(main())

In the example above, asyncio.as_completed returns an iterator that yields results as each task completes, allowing you to process them immediately. This is useful when you want to handle results as soon as they're available, rather than waiting for all tasks to finish. In the example, the tasks are run simultaneously, and their results are printed as each one finishes, in the order they complete.

So to make a summary, you use:

  • asyncio.wait: when you need to handle multiple tasks and want to track which tasks are completed and which are still pending. It's useful when you care about the status of each task separately.

  • asyncio.gather: when you want to run multiple tasks concurrently and need the results in a list, especially when the order of results matters or you need to handle exceptions gracefully.

  • asyncio.as_completed: when you want to process results as soon as each task finishes, rather than waiting for all tasks to complete. It’s useful for handling results in the order they become available.

However, these methods don't take atomic task management with built-in error handling. In the next section, we will see about asyncio.TaskGroup and how to use it to manage a group of tasks.

asyncio.TaskGroup

asyncio.TaskGroup is a context manager introduced in Python 3.11 that simplifies managing multiple tasks as a group. It ensures that if any task within the group fails, all other tasks are canceled, providing a way to handle complex task management with robust error handling. The class has one method called created_task used to create and add tasks to the task group. You pass a coroutine to this method, and it returns an asyncio.Task object that is managed by the group.

Here is an example of how it is used:

import asyncio

async def task1():
    await asyncio.sleep(1)
    return "Task 1 done"

async def task2():
    await asyncio.sleep(2)
    return "Task 2 done"

async def task_with_error():
    await asyncio.sleep(1)
    raise ValueError("An error occurred")

async def main():
    try:
        async with asyncio.TaskGroup() as tg:
            task1 = tg.create_task(task1())
            task2 = tg.create_task(task2())
            error_task = tg.create_task(task_with_error())
    except Exception as e:
        print(f"Error: {e}")

    # Print results from completed tasks
    print("Task 1 result:", task1.result())
    print("Task 2 result:", task2.result())

asyncio.run(main())

asyncio.TaskGroup manages multiple tasks and ensures that if any task fails, all other tasks in the group are canceled. In the example, a task with an error causes the entire group to be canceled, and only the results of completed tasks are printed.

Usage for this can be in web scraping. You can use asyncio.TaskGroup to handle multiple concurrent API requests and ensure that if any request fails, all other requests are canceled to avoid incomplete data.

We are at the end of the article and we have learned the multiple methods asyncio provides to create and manage tasks. Here is a summary of the methods:

  • asyncio.wait_for: Wait for a task with a timeout.

  • asyncio.wait: Wait for multiple tasks with flexible completion conditions.

  • asyncio.gather: Aggregate multiple tasks into a single awaitable.

  • asyncio.as_completed : gérez les tâches au fur et à mesure qu'elles sont terminées.

  • asyncio.TaskGroup : Gérer un groupe de tâches avec annulation automatique en cas d'échec.

Conclusion

La programmation asynchrone peut transformer la façon dont vous gérez les tâches simultanées en Python, rendant votre code plus efficace et plus réactif. Dans cet article, nous avons parcouru les différentes méthodes fournies par asyncio pour créer et gérer des tâches, des simples délais d'attente aux groupes de tâches sophistiqués. Comprendre quand et comment utiliser chaque méthode (asyncio.wait_for, asyncio.wait, asyncio.gather, asyncio.as_completed et asyncio.TaskGroup) vous aidera à exploiter tout le potentiel de la programmation asynchrone, rendant vos applications plus robustes et évolutives.

Pour une plongée plus approfondie dans la programmation asynchrone et des exemples plus pratiques, explorez notre guide détaillé ici.

Si vous avez apprécié cet article, pensez à vous inscrire à ma newsletter pour ne pas manquer les futures mises à jour.

Bon codage !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn