Coroutines et tâches
********************

Cette section donne un aperçu des API de haut-niveau du module
*asyncio* pour utiliser les coroutines et les tâches.

* Coroutines

* *Attendables*

* Création de tâches

* Annulation de tâche

* Groupes de tâches

* Attente

* Exécution de tâches de manière concurrente

* Eager Task Factory

* Protection contre l'annulation

* Délais d'attente

* Primitives d'attente

* Exécution dans des fils d'exécution (*threads*)

* Planification depuis d'autres fils d'exécution

* Introspection

* Objets *Task*


Coroutines
==========

**Code source :** Lib/asyncio/coroutines.py

======================================================================

Les *coroutines* déclarées avec la syntaxe *async/await* sont la
manière privilégiée d’écrire des applications asynchrones. Par
exemple, l’extrait de code suivant affiche « hello », attend une
seconde et affiche ensuite « world » :

   >>> import asyncio

   >>> async def main():
   ...     print('hello')
   ...     await asyncio.sleep(1)
   ...     print('world')

   >>> asyncio.run(main())
   hello
   world

Remarquez que simplement appeler une coroutine ne la planifie pas pour
exécution :

   >>> main()
   <coroutine object main at 0x1053bb7c8>

Pour réellement exécuter une coroutine, *asyncio* fournit les
mécanismes suivants :

* La fonction "asyncio.run()" pour exécuter la fonction « main() », le
  point d'entrée de haut-niveau (voir l'exemple ci-dessus).

* Attendre une coroutine. Le morceau de code suivant attend une
  seconde, affiche « hello », attend 2 secondes *supplémentaires*,
  puis affiche enfin « world » :

     import asyncio
     import time

     async def say_after(delay, what):
         await asyncio.sleep(delay)
         print(what)

     async def main():
         print(f"started at {time.strftime('%X')}")

         await say_after(1, 'hello')
         await say_after(2, 'world')

         print(f"finished at {time.strftime('%X')}")

     asyncio.run(main())

  Sortie attendue :

     started at 17:13:52
     hello
     world
     finished at 17:13:55

* La fonction "asyncio.create_task()" pour exécuter de manière
  concurrente des coroutines en tant que "tâches" *asyncio*.

  Modifions l'exemple ci-dessus et lançons deux coroutines "say_after"
  *de manière concurrente* :

     async def main():
         task1 = asyncio.create_task(
             say_after(1, 'hello'))

         task2 = asyncio.create_task(
             say_after(2, 'world'))

         print(f"started at {time.strftime('%X')}")

         # Wait until both tasks are completed (should take
         # around 2 seconds.)
         await task1
         await task2

         print(f"finished at {time.strftime('%X')}")

  La sortie attendue montre à présent que ce code s'exécute une
  seconde plus rapidement que le précédent :

     started at 17:14:32
     hello
     world
     finished at 17:14:34

* La classe "asyncio.TaskGroup" fournit une alternative plus moderne à
  "create_task()". En utilisant cette API, le dernier exemple devient
  :

     async def main():
         async with asyncio.TaskGroup() as tg:
             task1 = tg.create_task(
                 say_after(1, 'hello'))

             task2 = tg.create_task(
                 say_after(2, 'world'))

             print(f"started at {time.strftime('%X')}")

         # The await is implicit when the context manager exits.

         print(f"finished at {time.strftime('%X')}")

  Le temps d'exécution et la sortie doivent être les mêmes que pour la
  version précédente.

  Ajouté dans la version 3.11: "asyncio.TaskGroup".


*Attendables*
=============

Un objet est dit *attendable* (*awaitable* en anglais, c.-à-d. qui
peut être attendu) s'il peut être utilisé dans une expression "await".
Beaucoup d'API d'*asyncio* sont conçues pour accepter des
*attendables*.

Il existe trois types principaux d'*attendables* : les **coroutines**,
les **tâches** et les **futurs**.

-[ Coroutines ]-

Les coroutines sont des *awaitables* et peuvent donc être attendues
par d'autres coroutines :

   import asyncio

   async def nested():
       return 42

   async def main():
       # Nothing happens if we just call "nested()".
       # A coroutine object is created but not awaited,
       # so it *won't run at all*.
       nested()  # will raise a "RuntimeWarning".

       # Let's do it differently now and await it:
       print(await nested())  # will print "42".

   asyncio.run(main())

Important:

  dans cette documentation, le terme « coroutine » est utilisé pour
  désigner deux concepts voisins :

  * une *fonction coroutine* : une fonction "async def" ;

  * un *objet coroutine* : un objet renvoyé par une *fonction
    coroutine*.

-[ Tâches ]-

Les *tâches* servent à planifier des coroutines de façon à ce qu'elles
s'exécutent de manière concurrente.

Lorsqu'une coroutine est encapsulée dans une *tâche* à l'aide de
fonctions comme "asyncio.create_task()", la coroutine est
automatiquement planifiée pour s'exécuter prochainement :

   import asyncio

   async def nested():
       return 42

   async def main():
       # Schedule nested() to run soon concurrently
       # with "main()".
       task = asyncio.create_task(nested())

       # "task" can now be used to cancel "nested()", or
       # can simply be awaited to wait until it is complete:
       await task

   asyncio.run(main())

-[ Futurs ]-

Un "Future" est un objet *attendable* spécial de **bas-niveau**, qui
représente le **résultat final** d'une opération asynchrone.

Quand un objet *Future* est *attendu*, cela signifie que la coroutine
attendra que ce futur soit résolu à un autre endroit.

Les objets *Future* d'*asyncio* sont nécessaires pour permettre
l'exécution de code basé sur les fonctions de rappel avec la syntaxe
*async* / *await*.

Il est normalement **inutile** de créer des objets *Future* dans la
couche applicative du code.

Les objets *Future*, parfois exposés par des bibliothèques et quelques
API d'*asyncio*, peuvent être attendus :

   async def main():
       await function_that_returns_a_future_object()

       # this is also valid:
       await asyncio.gather(
           function_that_returns_a_future_object(),
           some_python_coroutine()
       )

"loop.run_in_executor()" est l'exemple typique d'une fonction bas-
niveau renvoyant un objet *Future*.


Création de tâches
==================

**Code source :** Lib/asyncio/tasks.py

======================================================================

asyncio.create_task(coro, *, name=None, context=None)

   Encapsule la coroutine *coro* dans une tâche et la planifie pour
   exécution. Renvoie l'objet "Task".

   Si *name* n’est pas "None", il est défini comme le nom de la tâche
   en utilisant "Task.set_name()".

   L'argument (uniquement nommé) facultatif *context* permet de
   spécifier un "contextvars.Context" personnalisé pour la coroutine à
   exécuter. La copie de contexte actuelle est créée lorsqu'aucun
   *context* n'est fourni.

   La tâche est exécutée dans la boucle renvoyée par
   "get_running_loop()" ; "RuntimeError" est levée s'il n'y a pas de
   boucle en cours d'exécution dans le fil actuel.

   Note:

     "asyncio.TaskGroup.create_task()" is a new alternative leveraging
     structural concurrency; it allows for waiting for a group of
     related tasks with strong safety guarantees.

   Important:

     gardez une référence au résultat de cette fonction, pour éviter
     qu'une tâche ne disparaisse en cours d'exécution. La boucle
     d'événements ne conserve que les références faibles aux tâches.
     Une tâche qui n'est pas référencée ailleurs peut être supprimée
     par le ramasse-miettes à tout moment, même avant qu'elle ne soit
     terminée. Pour créer des tâches d'arrière-plan fiables de type «
     lance et oublie », rassemblez-les dans une collection :

        background_tasks = set()

        for i in range(10):
            task = asyncio.create_task(some_coro(param=i))

            # Add task to the set. This creates a strong reference.
            background_tasks.add(task)

            # To prevent keeping references to finished tasks forever,
            # make each task remove its own reference from the set after
            # completion:
            task.add_done_callback(background_tasks.discard)

   Ajouté dans la version 3.7.

   Modifié dans la version 3.8: ajout du paramètre *name*.

   Modifié dans la version 3.11: ajout du paramètre *context*.


Annulation de tâche
===================

Les tâches peuvent être annulées facilement et en toute sécurité.
Lorsqu'une tâche est annulée, "asyncio.CancelledError" est levée dans
la tâche à la première occasion.

Il est recommandé que les coroutines utilisent des blocs "try/finally"
pour exécuter de manière robuste la logique de nettoyage. Dans le cas
où "asyncio.CancelledError" est explicitement interceptée, elle
devrait généralement être propagée lorsque le nettoyage est terminé.
"asyncio.CancelledError" sous-classe directement "BaseException" donc
la plupart du code n'a pas besoin d'en être conscient.

Les composants *asyncio* qui permettent la concurrence structurée,
comme "asyncio.TaskGroup" et "asyncio.timeout()", sont implémentés en
utilisant l'annulation en interne et peuvent mal se comporter si une
coroutine ne propage pas "asyncio.CancelledError". De même, le code
utilisateur ne doit généralement pas appeler "uncancel". Cependant,
dans les cas où la suppression de "asyncio.CancelledError" est
vraiment souhaitée, il est également nécessaire d'appeler "uncancel()"
pour supprimer complètement l'état d'annulation.


Groupes de tâches
=================

Les groupes de tâches combinent une API de création de tâches avec un
moyen pratique et fiable d'attendre la fin de toutes les tâches du
groupe.

class asyncio.TaskGroup

   Gestionnaire de contexte asynchrone responsable d’un groupe de
   tâches. Des tâches peuvent être ajoutées au groupe en utilisant
   "create_task()". Toutes les tâches sont attendues à la sortie du
   gestionnaire de contexte.

   Ajouté dans la version 3.11.

   create_task(coro, *, name=None, context=None)

      Create a task in this task group. The signature matches that of
      "asyncio.create_task()". If the task group is inactive (e.g. not
      yet entered, already finished, or in the process of shutting
      down), we will close the given "coro".

      Modifié dans la version 3.13: Close the given coroutine if the
      task group is not active.

Exemple :

   async def main():
       async with asyncio.TaskGroup() as tg:
           task1 = tg.create_task(some_coro(...))
           task2 = tg.create_task(another_coro(...))
       print(f"Both tasks have completed now: {task1.result()}, {task2.result()}")

L'instruction "async with" attend la fin de toutes les tâches du
groupe. Lors de l'attente, de nouvelles tâches peuvent encore être
ajoutées au groupe (par exemple, en passant "tg" dans l'une des
coroutines et en appelant "tg.create_task()" dans cette coroutine).
Une fois que la dernière tâche est terminée et que le bloc "async
with" est quitté, aucune nouvelle tâche ne peut être ajoutée au
groupe.

La première fois que l'une des tâches appartenant au groupe échoue
avec une exception autre que "asyncio.CancelledError", les tâches
restantes du groupe sont annulées. Aucune autre tâche ne peut alors
être ajoutée au groupe. À ce stade, si le corps de l'instruction
"async with" est toujours actif (par exemple, "__aexit__()" n'a pas
encore été appelé), la tâche contenant directement l'instruction
"async with" est également annulée. Le résultat
"asyncio.CancelledError" interrompt un "await", mais il ne sort pas de
l'instruction "async with" englobante.

Une fois toutes les tâches terminées, si des tâches ont échoué avec
une exception autre que "asyncio.CancelledError", ces exceptions sont
combinées dans un "ExceptionGroup" ou "BaseExceptionGroup" (selon le
cas ; voir leur documentation) qui est ensuite levé.

Deux exceptions de base sont traitées spécialement : si une tâche
échoue avec "KeyboardInterrupt" ou "SystemExit", le groupe de tâches
annule toujours les tâches restantes et les attend, mais alors la
"KeyboardInterrupt" ou la "SystemExit" initiale est levée à nouveau au
lieu de "ExceptionGroup" ou "BaseExceptionGroup".

Si le corps de l'instruction "async with" se termine avec une
exception (donc "__aexit__()" est appelé avec un ensemble
d'exceptions), cela est traité de la même manière que si l'une des
tâches échouait : les tâches restantes sont annulées puis attendues,
et les exceptions de non-annulation sont regroupées dans un groupe
d'exceptions et levées. L'exception transmise à "__aexit__()", à moins
qu'il ne s'agisse de "asyncio.CancelledError", est également incluse
dans le groupe d'exceptions. Le même cas spécial concerne
"KeyboardInterrupt" et "SystemExit" comme dans le paragraphe
précédent.

Task groups are careful not to mix up the internal cancellation used
to "wake up" their "__aexit__()" with cancellation requests for the
task in which they are running made by other parties. In particular,
when one task group is syntactically nested in another, and both
experience an exception in one of their child tasks simultaneously,
the inner task group will process its exceptions, and then the outer
task group will receive another cancellation and process its own
exceptions.

In the case where a task group is cancelled externally and also must
raise an "ExceptionGroup", it will call the parent task's "cancel()"
method. This ensures that a "asyncio.CancelledError" will be raised at
the next "await", so the cancellation is not lost.

Task groups preserve the cancellation count reported by
"asyncio.Task.cancelling()".

Modifié dans la version 3.13: Improved handling of simultaneous
internal and external cancellations and correct preservation of
cancellation counts.


Terminating a Task Group
------------------------

While terminating a task group is not natively supported by the
standard library, termination can be achieved by adding an exception-
raising task to the task group and ignoring the raised exception:

   import asyncio
   from asyncio import TaskGroup

   class TerminateTaskGroup(Exception):
       """Exception raised to terminate a task group."""

   async def force_terminate_task_group():
       """Used to force termination of a task group."""
       raise TerminateTaskGroup()

   async def job(task_id, sleep_time):
       print(f'Task {task_id}: start')
       await asyncio.sleep(sleep_time)
       print(f'Task {task_id}: done')

   async def main():
       try:
           async with TaskGroup() as group:
               # spawn some tasks
               group.create_task(job(1, 0.5))
               group.create_task(job(2, 1.5))
               # sleep for 1 second
               await asyncio.sleep(1)
               # add an exception-raising task to force the group to terminate
               group.create_task(force_terminate_task_group())
       except* TerminateTaskGroup:
           pass

   asyncio.run(main())

Expected output:

   Task 1: start
   Task 2: start
   Task 1: done


Attente
=======

async asyncio.sleep(delay, result=None)

   Attend pendant *delay* secondes.

   Si *result* est spécifié, il est renvoyé à l'appelant quand la
   coroutine se termine.

   "sleep()" suspend systématiquement la tâche courante, ce qui permet
   aux autres tâches de s'exécuter.

   Définir le délai sur 0 fournit un chemin optimisé pour permettre à
   d'autres tâches de s'exécuter. Cela peut être utilisé par les
   fonctions de longue durée pour éviter de bloquer la boucle
   d'événements pendant toute la durée de l'appel de fonction.

   Exemple d'une coroutine affichant la date toutes les secondes
   pendant 5 secondes :

      import asyncio
      import datetime

      async def display_date():
          loop = asyncio.get_running_loop()
          end_time = loop.time() + 5.0
          while True:
              print(datetime.datetime.now())
              if (loop.time() + 1.0) >= end_time:
                  break
              await asyncio.sleep(1)

      asyncio.run(display_date())

   Modifié dans la version 3.10: le paramètre *loop* a été enlevé.

   Modifié dans la version 3.13: Raises "ValueError" if *delay* is
   "nan".


Exécution de tâches de manière concurrente
==========================================

awaitable asyncio.gather(*aws, return_exceptions=False)

   Exécute les objets awaitable de la séquence *aws*, *de manière
   concurrente*.

   Si un *attendable* de *aws* est une coroutine, celui-ci est
   automatiquement planifié comme une tâche *Task*.

   Si tous les *awaitables* s'achèvent avec succès, le résultat est la
   liste des valeurs renvoyées. L'ordre de cette liste correspond à
   l'ordre des *awaitables* dans *aws*.

   Si *return_exceptions* vaut "False" (valeur par défaut), la
   première exception levée est immédiatement propagée vers la tâche
   en attente dans le "gather()". Les autres *attendables* dans la
   séquence *aws* **ne sont pas annulés** et poursuivent leur
   exécution.

   Si *return_exceptions* vaut "True", les exceptions sont traitées de
   la même manière que les exécutions normales, et incluses dans la
   liste des résultats.

   Si "gather()" est *annulé*, tous les *awaitables* en cours (ceux
   qui n'ont pas encore fini de s'exécuter) sont également *annulés*.

   Si n'importe quel *Task* ou *Future* de la séquence *aws* est
   *annulé*, il est traité comme s'il avait levé "CancelledError" —
   l'appel à "gather()" n'est alors **pas** annulé. Ceci permet
   d'empêcher que l'annulation d'une tâche ou d'un futur entraîne
   l'annulation des autres tâches ou futurs.

   Note:

     A new alternative to create and run tasks concurrently and wait
     for their completion is "asyncio.TaskGroup". *TaskGroup* provides
     stronger safety guarantees than *gather* for scheduling a nesting
     of subtasks: if a task (or a subtask, a task scheduled by a task)
     raises an exception, *TaskGroup* will, while *gather* will not,
     cancel the remaining scheduled tasks).

   Exemple :

      import asyncio

      async def factorial(name, number):
          f = 1
          for i in range(2, number + 1):
              print(f"Task {name}: Compute factorial({number}), currently i={i}...")
              await asyncio.sleep(1)
              f *= i
          print(f"Task {name}: factorial({number}) = {f}")
          return f

      async def main():
          # Schedule three calls *concurrently*:
          L = await asyncio.gather(
              factorial("A", 2),
              factorial("B", 3),
              factorial("C", 4),
          )
          print(L)

      asyncio.run(main())

      # Expected output:
      #
      #     Task A: Compute factorial(2), currently i=2...
      #     Task B: Compute factorial(3), currently i=2...
      #     Task C: Compute factorial(4), currently i=2...
      #     Task A: factorial(2) = 2
      #     Task B: Compute factorial(3), currently i=3...
      #     Task C: Compute factorial(4), currently i=3...
      #     Task B: factorial(3) = 6
      #     Task C: Compute factorial(4), currently i=4...
      #     Task C: factorial(4) = 24
      #     [2, 6, 24]

   Note:

     If *return_exceptions* is false, cancelling gather() after it has
     been marked done won't cancel any submitted awaitables. For
     instance, gather can be marked done after propagating an
     exception to the caller, therefore, calling "gather.cancel()"
     after catching an exception (raised by one of the awaitables)
     from gather won't cancel any other awaitables.

   Modifié dans la version 3.7: Si *gather* est lui-même annulé,
   l'annulation est propagée indépendamment de *return_exceptions*.

   Modifié dans la version 3.10: le paramètre *loop* a été enlevé.

   Obsolète depuis la version 3.10: Un avertissement d'obsolescence
   est émis si aucun argument positionnel n'est fourni ou si tous les
   arguments positionnels ne sont pas des objets de type Future et
   qu'il n'y a pas de boucle d'événements en cours d'exécution.


Eager Task Factory
==================

asyncio.eager_task_factory(loop, coro, *, name=None, context=None)

   A task factory for eager task execution.

   When using this factory (via
   "loop.set_task_factory(asyncio.eager_task_factory)"), coroutines
   begin execution synchronously during "Task" construction. Tasks are
   only scheduled on the event loop if they block. This can be a
   performance improvement as the overhead of loop scheduling is
   avoided for coroutines that complete synchronously.

   A common example where this is beneficial is coroutines which
   employ caching or memoization to avoid actual I/O when possible.

   Note:

     Immediate execution of the coroutine is a semantic change. If the
     coroutine returns or raises, the task is never scheduled to the
     event loop. If the coroutine execution blocks, the task is
     scheduled to the event loop. This change may introduce behavior
     changes to existing applications. For example, the application's
     task execution order is likely to change.

   Ajouté dans la version 3.12.

asyncio.create_eager_task_factory(custom_task_constructor)

   Create an eager task factory, similar to "eager_task_factory()",
   using the provided *custom_task_constructor* when creating a new
   task instead of the default "Task".

   *custom_task_constructor* must be a *callable* with the signature
   matching the signature of "Task.__init__". The callable must return
   a "asyncio.Task"-compatible object.

   This function returns a *callable* intended to be used as a task
   factory of an event loop via "loop.set_task_factory(factory)").

   Ajouté dans la version 3.12.


Protection contre l'annulation
==============================

awaitable asyncio.shield(aw)

   Empêche qu'un objet awaitable puisse être "annulé".

   Si *aw* est une coroutine, elle est planifiée automatiquement comme
   une tâche.

   L'instruction :

      task = asyncio.create_task(something())
      res = await shield(task)

   est équivalente à :

      res = await something()

   *à la différence près* que, si la coroutine qui la contient est
   annulée, la tâche s'exécutant dans "something()" n'est pas annulée.
   Du point de vue de "something()", il n'y a pas eu d'annulation.
   Cependant, son appelant est bien annulé, donc l'expression *await*
   lève bien une "CancelledError".

   Si "something()" est annulée d'une autre façon (c.-à-d. depuis
   elle-même) ceci annule également "shield()".

   Pour ignorer complètement l'annulation (déconseillé), la fonction
   "shield()" peut être combinée à une clause *try* / *except*, comme
   dans le code ci-dessous :

      task = asyncio.create_task(something())
      try:
          res = await shield(task)
      except CancelledError:
          res = None

   Important:

     sauvegardez une référence aux tâches passées à cette fonction,
     pour éviter qu'une tâche ne disparaisse en cours d'exécution. La
     boucle d'événements ne conserve que les références faibles aux
     tâches. Une tâche qui n'est pas référencée ailleurs peut faire
     l'objet d'une suppression par le ramasse-miettes à tout moment,
     même avant qu'elle ne soit terminée.

   Modifié dans la version 3.10: le paramètre *loop* a été enlevé.

   Obsolète depuis la version 3.10: un avertissement d'obsolescence
   est émis si *aw* n'est pas un objet de type *future* et qu'il n'y a
   pas de boucle d'événement en cours d'exécution.


Délais d'attente
================

asyncio.timeout(delay)

   Return an asynchronous context manager that can be used to limit
   the amount of time spent waiting on something.

   *delay* peut-être soit "None", soit le nombre de secondes (entier
   ou décimal) d'attente. Si *delay* vaut "None", aucune limite n'est
   appliquée ; cela peut être utile lorsque le délai d'attente est
   inconnu au moment de la création du gestionnaire de contexte.

   Dans les deux cas, le gestionnaire de contexte peut être redéfini
   après sa création en utilisant "Timeout.reschedule()".

   Exemple :

      async def main():
          async with asyncio.timeout(10):
              await long_running_task()

   If "long_running_task" takes more than 10 seconds to complete, the
   context manager will cancel the current task and handle the
   resulting "asyncio.CancelledError" internally, transforming it into
   a "TimeoutError" which can be caught and handled.

   Note:

     The "asyncio.timeout()" context manager is what transforms the
     "asyncio.CancelledError" into a "TimeoutError", which means the
     "TimeoutError" can only be caught *outside* of the context
     manager.

   Example of catching "TimeoutError":

      async def main():
          try:
              async with asyncio.timeout(10):
                  await long_running_task()
          except TimeoutError:
              print("The long operation timed out, but we've handled it.")

          print("This statement will run regardless.")

   Le gestionnaire de contexte produit par "asyncio.timeout()" peut
   être reprogrammé à une échéance différente et inspecté.

   class asyncio.Timeout(when)

      Gestionnaire de contexte asynchrone pour annuler les coroutines
      en retard.

      "when" doit être un temps absolu au bout duquel le contexte doit
      expirer, tel que mesuré par l'horloge de la boucle d'événements
      :

      * Si "when" vaut "None", le délai maximal ne se déclenche
        jamais.

      * Si "when < loop.time()", le délai maximal se déclenche à la
        prochaine itération de la boucle d'événement.

         when() -> float | None

            Renvoie la limite de temps d'exécution définie
            actuellement, ou "None" s'il n'y en a pas.

         reschedule(when: float | None)

            Reprogramme le délai d'attente.

         expired() -> bool

            Renvoie si le gestionnaire de contexte a dépassé son délai
            (c.-à-d. s'il a expiré).

   Exemple :

      async def main():
          try:
              # We do not know the timeout when starting, so we pass ``None``.
              async with asyncio.timeout(None) as cm:
                  # We know the timeout now, so we reschedule it.
                  new_deadline = get_running_loop().time() + 10
                  cm.reschedule(new_deadline)

                  await long_running_task()
          except TimeoutError:
              pass

          if cm.expired():
              print("Looks like we haven't finished on time.")

   Les gestionnaires de contexte de délai maximal peuvent être
   imbriqués en toute sécurité.

   Ajouté dans la version 3.11.

asyncio.timeout_at(when)

   Semblable à "asyncio.timeout()", sauf que *when* est le temps
   absolu pour arrêter d'attendre, ou "None".

   Exemple :

      async def main():
          loop = get_running_loop()
          deadline = loop.time() + 20
          try:
              async with asyncio.timeout_at(deadline):
                  await long_running_task()
          except TimeoutError:
              print("The long operation timed out, but we've handled it.")

          print("This statement will run regardless.")

   Ajouté dans la version 3.11.

async asyncio.wait_for(aw, timeout)

   Attend la fin de l'awaitable *aw* avec délai d'attente.

   Si *aw* est une coroutine, elle est planifiée automatiquement comme
   une tâche.

   *timeout* peut-être soit "None", soit le nombre de secondes (entier
   ou décimal) d'attente. Si *timeout* vaut "None", la fonction
   s'interrompt jusqu'à ce que le futur s'achève.

   Si le délai d'attente maximal est dépassé, la tâche est annulée et
   l'exception "TimeoutError" est levée.

   Pour empêcher "l'annulation" de la tâche, il est nécessaire de
   l'encapsuler dans une fonction "shield()".

   Cette fonction attend que le futur soit réellement annulé, donc le
   temps d'attente total peut être supérieur à *timeout*. Si une
   exception se produit lors de l'annulation, elle est propagée.

   Si l'attente est annulée, le futur *aw* est également annulé.

   Exemple :

      async def eternity():
          # Sleep for one hour
          await asyncio.sleep(3600)
          print('yay!')

      async def main():
          # Wait for at most 1 second
          try:
              await asyncio.wait_for(eternity(), timeout=1.0)
          except TimeoutError:
              print('timeout!')

      asyncio.run(main())

      # Expected output:
      #
      #     timeout!

   Modifié dans la version 3.7: Si le dépassement du délai d'attente
   maximal provoque l'annulation de *aw*, "wait_for" attend que *aw*
   soit annulée. Auparavant, l'exception "TimeoutError" était
   immédiatement levée.

   Modifié dans la version 3.10: le paramètre *loop* a été enlevé.

   Modifié dans la version 3.11: Raises "TimeoutError" instead of
   "asyncio.TimeoutError".


Primitives d'attente
====================

async asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED)

   Exécute les instances "Future" et "Task" de l'itérable *aws* de
   manière concurrente, et s'interrompt jusqu'à ce que la condition
   décrite dans *return_when* soit vraie.

   The *aws* iterable must not be empty.

   Renvoie deux ensembles de *Tasks* / *Futures* : "(done, pending)".

   Utilisation :

      done, pending = await asyncio.wait(aws)

   *timeout* (entier ou décimal), si précisé, peut-être utilisé pour
   contrôler le nombre maximal de secondes d'attente avant de se
   terminer.

   Cette fonction ne lève pas "TimeoutError". Les futurs et les tâches
   qui ne sont pas finis quand le délai d'attente maximal est dépassé
   sont tout simplement renvoyés dans le second ensemble.

   *return_when* indique quand la fonction doit se terminer. Il peut
   prendre les valeurs suivantes :

   +----------------------------------------------------+----------------------------------------------------+
   | Constante                                          | Description                                        |
   |====================================================|====================================================|
   | asyncio.FIRST_COMPLETED                            | La fonction se termine lorsque n'importe quel      |
   |                                                    | futur se termine ou est annulé.                    |
   +----------------------------------------------------+----------------------------------------------------+
   | asyncio.FIRST_EXCEPTION                            | The function will return when any future finishes  |
   |                                                    | by raising an exception. If no future raises an    |
   |                                                    | exception then it is equivalent to                 |
   |                                                    | "ALL_COMPLETED".                                   |
   +----------------------------------------------------+----------------------------------------------------+
   | asyncio.ALL_COMPLETED                              | La fonction se termine lorsque les *futurs* sont   |
   |                                                    | tous finis ou annulés.                             |
   +----------------------------------------------------+----------------------------------------------------+

   À la différence de "wait_for()", "wait()" n'annule pas les futurs
   quand le délai d'attente est dépassé.

   Modifié dans la version 3.10: le paramètre *loop* a été enlevé.

   Modifié dans la version 3.11: Passer directement des objets
   coroutines à "wait()" est interdit.

   Modifié dans la version 3.12: Added support for generators yielding
   tasks.

asyncio.as_completed(aws, *, timeout=None)

   Run awaitable objects in the *aws* iterable concurrently. The
   returned object can be iterated to obtain the results of the
   awaitables as they finish.

   The object returned by "as_completed()" can be iterated as an
   *asynchronous iterator* or a plain *iterator*. When asynchronous
   iteration is used, the originally-supplied awaitables are yielded
   if they are tasks or futures. This makes it easy to correlate
   previously-scheduled tasks with their results. Example:

      ipv4_connect = create_task(open_connection("127.0.0.1", 80))
      ipv6_connect = create_task(open_connection("::1", 80))
      tasks = [ipv4_connect, ipv6_connect]

      async for earliest_connect in as_completed(tasks):
          # earliest_connect is done. The result can be obtained by
          # awaiting it or calling earliest_connect.result()
          reader, writer = await earliest_connect

          if earliest_connect is ipv6_connect:
              print("IPv6 connection established.")
          else:
              print("IPv4 connection established.")

   During asynchronous iteration, implicitly-created tasks will be
   yielded for supplied awaitables that aren't tasks or futures.

   When used as a plain iterator, each iteration yields a new
   coroutine that returns the result or raises the exception of the
   next completed awaitable. This pattern is compatible with Python
   versions older than 3.13:

      ipv4_connect = create_task(open_connection("127.0.0.1", 80))
      ipv6_connect = create_task(open_connection("::1", 80))
      tasks = [ipv4_connect, ipv6_connect]

      for next_connect in as_completed(tasks):
          # next_connect is not one of the original task objects. It must be
          # awaited to obtain the result value or raise the exception of the
          # awaitable that finishes next.
          reader, writer = await next_connect

   A "TimeoutError" is raised if the timeout occurs before all
   awaitables are done. This is raised by the "async for" loop during
   asynchronous iteration or by the coroutines yielded during plain
   iteration.

   Modifié dans la version 3.10: le paramètre *loop* a été enlevé.

   Obsolète depuis la version 3.10: Un avertissement d'obsolescence
   est émis si tous les objets en attente dans l'itérable *aws* ne
   sont pas des objets de type *Future* et qu'il n'y a pas de boucle
   d'événement en cours d'exécution.

   Modifié dans la version 3.12: Added support for generators yielding
   tasks.

   Modifié dans la version 3.13: The result can now be used as either
   an *asynchronous iterator* or as a plain *iterator* (previously it
   was only a plain iterator).


Exécution dans des fils d'exécution (*threads*)
===============================================

async asyncio.to_thread(func, /, *args, **kwargs)

   Exécute la fonction *func* de manière asynchrone dans un fil
   d'exécution séparé.

   Tous les **args* et ***kwargs* fournis à cette fonction sont
   directement passés à *func*. De plus, le "contextvars.Context"
   actuel est propagé, ce qui permet d'accéder aux variables de
   contexte du fil de boucle d'événements dans le fil séparé.

   Renvoie une coroutine qui peut être attendue pour obtenir le
   résultat éventuel de *func*.

   Cette fonction coroutine est principalement destinée à être
   utilisée pour exécuter des fonctions/méthodes faisant beaucoup d
   'entrées-sorties et qui bloqueraient autrement la boucle
   d'événements si elles étaient exécutées dans le fil d'exécution
   principal. Par exemple :

      def blocking_io():
          print(f"start blocking_io at {time.strftime('%X')}")
          # Note that time.sleep() can be replaced with any blocking
          # IO-bound operation, such as file operations.
          time.sleep(1)
          print(f"blocking_io complete at {time.strftime('%X')}")

      async def main():
          print(f"started main at {time.strftime('%X')}")

          await asyncio.gather(
              asyncio.to_thread(blocking_io),
              asyncio.sleep(1))

          print(f"finished main at {time.strftime('%X')}")


      asyncio.run(main())

      # Expected output:
      #
      # started main at 19:50:53
      # start blocking_io at 19:50:53
      # blocking_io complete at 19:50:54
      # finished main at 19:50:54

   Appeler directement "blocking_io()" dans n'importe quelle coroutine
   bloquerait la boucle d'événements pendant sa durée, ce qui
   entraînerait une seconde supplémentaire de temps d'exécution. Au
   lieu de cela, en utilisant "asyncio.to_thread()", nous pouvons
   l'exécuter dans un fil d'exécution séparé sans bloquer la boucle
   d'événements.

   Note:

     en raison du *GIL*, "asyncio.to_thread()" ne peut généralement
     être utilisée que pour rendre les fonctions faisant beaucoup d
     'entrées-sorties non bloquantes. Cependant, pour les modules
     d'extension qui relâchent le GIL ou les implémentations Python
     alternatives qui n'en ont pas, "asyncio.to_thread()" peut
     également être utilisée pour les fonctions qui sollicitent
     beaucoup le processeur.

   Ajouté dans la version 3.9.


Planification depuis d'autres fils d'exécution
==============================================

asyncio.run_coroutine_threadsafe(coro, loop)

   Enregistre une coroutine dans la boucle d'exécution actuelle. Cette
   opération est compatible avec les programmes à multiples fils
   d'exécution (*thread-safe*).

   Renvoie un "concurrent.futures.Future" pour attendre le résultat
   d'un autre fil d'exécution du système d'exploitation.

   Cette fonction est faite pour être appelée par un fil d'exécution
   distinct de celui dans laquelle la boucle d'événement s'exécute.
   Exemple :

      # Create a coroutine
      coro = asyncio.sleep(1, result=3)

      # Submit the coroutine to a given loop
      future = asyncio.run_coroutine_threadsafe(coro, loop)

      # Wait for the result with an optional timeout argument
      assert future.result(timeout) == 3

   Si une exception est levée dans une coroutine, le futur renvoyé en
   sera averti. Elle peut également être utilisée pour annuler la
   tâche de la boucle d'événement :

      try:
          result = future.result(timeout)
      except TimeoutError:
          print('The coroutine took too long, cancelling the task...')
          future.cancel()
      except Exception as exc:
          print(f'The coroutine raised an exception: {exc!r}')
      else:
          print(f'The coroutine returned: {result!r}')

   Voir la section exécution concurrente et multi-fils d'exécution de
   la documentation.

   À la différence des autres fonctions d'*asyncio*, cette fonction
   requiert que *loop* soit passé de manière explicite.

   Ajouté dans la version 3.5.1.


Introspection
=============

asyncio.current_task(loop=None)

   Renvoie l'instance de la "Task" en cours d'exécution, ou "None"
   s'il n'y a pas de tâche en cours.

   Si *loop* vaut "None", "get_running_loop()" est appelée pour
   récupérer la boucle en cours d'exécution.

   Ajouté dans la version 3.7.

asyncio.all_tasks(loop=None)

   Renvoie l'ensemble des "Task" non terminés en cours d'exécution
   dans la boucle.

   Si *loop* vaut "None", "get_running_loop()" est appelée pour
   récupérer la boucle en cours d'exécution.

   Ajouté dans la version 3.7.

asyncio.iscoroutine(obj)

   Renvoie "True" si *obj* est un objet coroutine.

   Ajouté dans la version 3.4.


Objets *Task*
=============

class asyncio.Task(coro, *, loop=None, name=None, context=None, eager_start=False)

   Objet compatible avec "Future" qui exécute une coroutine Python.
   Cet objet n'est pas utilisable dans des programmes à fils
   d'exécution multiples.

   Les tâches servent à exécuter des coroutines dans des boucles
   d'événements. Si une coroutine attend un futur, la tâche interrompt
   son exécution et attend la fin de ce *futur*. Quand celui-ci est
   terminé, l'exécution de la coroutine encapsulée reprend.

   Les boucles d'événement fonctionnent de manière *coopérative* : une
   boucle d'événement exécute une tâche à la fois. Quand une tâche
   attend la fin d'un futur, la boucle d'événement exécute d'autres
   tâches, des fonctions de rappel, ou effectue des opérations d
   'entrées-sorties.

   La fonction de haut niveau "asyncio.create_task()" et les fonctions
   de bas-niveau "loop.create_task()" ou "ensure_future()" permettent
   de créer des tâches. Il est déconseillé d'instancier manuellement
   des objets *Task*.

   La méthode "cancel()" d'une tâche en cours d'exécution permet
   d'annuler celle-ci. L'appel de cette méthode force la tâche à lever
   l'exception "CancelledError" dans la coroutine encapsulée. Si la
   coroutine attendait un *futur* au moment de l'annulation, celui-ci
   est annulé.

   La méthode "cancelled()" permet de vérifier si la tâche a été
   annulée. Elle renvoie "True" si la coroutine encapsulée n'a pas
   ignoré l'exception "CancelledError" et a bien été annulée.

   "asyncio.Task" hérite de "Future", de toute son API, à l'exception
   de "Future.set_result()" et de "Future.set_exception()".

   An optional keyword-only *context* argument allows specifying a
   custom "contextvars.Context" for the *coro* to run in. If no
   *context* is provided, the Task copies the current context and
   later runs its coroutine in the copied context.

   An optional keyword-only *eager_start* argument allows eagerly
   starting the execution of the "asyncio.Task" at task creation time.
   If set to "True" and the event loop is running, the task will start
   executing the coroutine immediately, until the first time the
   coroutine blocks. If the coroutine returns or raises without
   blocking, the task will be finished eagerly and will skip
   scheduling to the event loop.

   Modifié dans la version 3.7: ajout du support du module
   "contextvars".

   Modifié dans la version 3.8: ajout du paramètre *name*.

   Obsolète depuis la version 3.10: un avertissement d'obsolescence
   est émis si *loop* n'est pas spécifié et qu'il n'y a pas de boucle
   d'événement en cours d'exécution.

   Modifié dans la version 3.11: ajout du paramètre *context*.

   Modifié dans la version 3.12: Added the *eager_start* parameter.

   done()

      Renvoie "True" si la tâche est *achevée*.

      Une tâche est dite *achevée* quand la coroutine encapsulée a
      soit renvoyé une valeur, soit levé une exception, ou que la
      tâche a été annulée.

   result()

      Renvoie le résultat de la tâche.

      Si la tâche est *achevée*, le résultat de la coroutine
      encapsulée est renvoyé (sinon, dans le cas où la coroutine a
      levé une exception, cette exception est de nouveau levée).

      Si la tâche a été *annulée*, cette méthode lève une exception
      "CancelledError".

      If the Task's result isn't yet available, this method raises an
      "InvalidStateError" exception.

   exception()

      Renvoie l'exception de la tâche.

      Si la coroutine encapsulée lève une exception, cette exception
      est renvoyée. Si la coroutine s'est exécutée normalement, cette
      méthode renvoie "None".

      Si la tâche a été *annulée*, cette méthode lève une exception
      "CancelledError".

      Si la tâche n'est pas encore *achevée*, cette méthode lève une
      exception "InvalidStateError".

   add_done_callback(callback, *, context=None)

      Ajoute une fonction de rappel qui sera exécutée quand la tâche
      sera *achevée*.

      Cette méthode ne doit être utilisée que dans du code basé sur
      les fonctions de rappel de bas-niveau.

      Se référer à la documentation de "Future.add_done_callback()"
      pour plus de détails.

   remove_done_callback(callback)

      Retire *callback* de la liste de fonctions de rappel.

      Cette méthode ne doit être utilisée que dans du code basé sur
      les fonctions de rappel de bas-niveau.

      Se référer à la documentation de "Future.remove_done_callback()"
      pour plus de détails.

   get_stack(*, limit=None)

      Renvoie une liste représentant la pile d'appels de la tâche.

      Si la coroutine encapsulée n'est pas terminée, cette fonction
      renvoie la pile d'appels à partir de l'endroit où celle-ci est
      interrompue. Si la coroutine s'est terminée normalement ou a été
      annulée, cette fonction renvoie une liste vide. Si la coroutine
      a été terminée par une exception, ceci renvoie la pile
      d'erreurs.

      La pile est toujours affichée de l'appelant à l'appelé.

      Une seule ligne est renvoyée si la coroutine est suspendue.

      L'argument facultatif *limit* définit le nombre maximal d'appels
      à renvoyer ; par défaut, tous sont renvoyés. L'ordre de la liste
      diffère selon la nature de celle-ci : les appels les plus
      récents d'une pile d'appels sont renvoyés, si la pile est une
      pile d'erreurs, ce sont les appels les plus anciens qui le sont
      (dans un souci de cohérence avec le module *traceback*).

   print_stack(*, limit=None, file=None)

      Affiche la pile d'appels ou d'erreurs de la tâche.

      Le format de sortie des appels produits par "get_stack()" est
      similaire à celui du module *traceback*.

      Le paramètre *limit* est directement passé à "get_stack()".

      Le paramètre *file* est un flux d'entrées-sorties sur lequel le
      résultat est écrit ; par défaut, "sys.stdout".

   get_coro()

      Renvoie l’objet *coroutine* encapsulé par la "Task".

      Note:

        This will return "None" for Tasks which have already completed
        eagerly. See the Eager Task Factory.

      Ajouté dans la version 3.8.

      Modifié dans la version 3.12: Newly added eager task execution
      means result may be "None".

   get_context()

      Return the "contextvars.Context" object associated with the
      task.

      Ajouté dans la version 3.12.

   get_name()

      Renvoie le nom de la tâche.

      Si aucun nom n’a été explicitement assigné à la tâche,
      l’implémentation par défaut d’une *Task* *asyncio* génère un nom
      par défaut durant l’instanciation.

      Ajouté dans la version 3.8.

   set_name(value)

      Définit le nom de la tâche.

      L’argument *value* peut être n’importe quel objet qui sera
      ensuite converti en chaine de caractères.

      Dans l’implémentation par défaut de *Task*, le nom sera visible
      dans le résultat de "repr()" d’un objet *Task*.

      Ajouté dans la version 3.8.

   cancel(msg=None)

      Demande l'annulation d'une tâche.

      If the Task is already *done* or *cancelled*, return "False",
      otherwise, return "True".

      The method arranges for a "CancelledError" exception to be
      thrown into the wrapped coroutine on the next cycle of the event
      loop.

      La coroutine peut alors faire le ménage ou même ignorer la
      requête en supprimant l'exception à l'aide d'un bloc "try" … …
      "except CancelledError" … "finally". Par conséquent,
      contrairement à "Future.cancel()", "Task.cancel()" ne garantit
      pas que la tâche sera annulée, bien qu'ignorer totalement une
      annulation ne soit ni une pratique courante, ni encouragé. Si la
      coroutine décide néanmoins de supprimer l'annulation, elle doit
      appeler "Task.uncancel()" en plus d'intercepter l'exception.

      Modifié dans la version 3.9: ajout du paramètre *msg*.

      Modifié dans la version 3.11: le paramètre "msg" est propagé de
      la tâche annulée vers celle qui l'attend.

      L'exemple ci-dessous illustre comment une coroutine peut
      intercepter une requête d'annulation :

         async def cancel_me():
             print('cancel_me(): before sleep')

             try:
                 # Wait for 1 hour
                 await asyncio.sleep(3600)
             except asyncio.CancelledError:
                 print('cancel_me(): cancel sleep')
                 raise
             finally:
                 print('cancel_me(): after sleep')

         async def main():
             # Create a "cancel_me" Task
             task = asyncio.create_task(cancel_me())

             # Wait for 1 second
             await asyncio.sleep(1)

             task.cancel()
             try:
                 await task
             except asyncio.CancelledError:
                 print("main(): cancel_me is cancelled now")

         asyncio.run(main())

         # Expected output:
         #
         #     cancel_me(): before sleep
         #     cancel_me(): cancel sleep
         #     cancel_me(): after sleep
         #     main(): cancel_me is cancelled now

   cancelled()

      Renvoie "True" si la tâche est *annulée*.

      La tâche est *annulée* quand l'annulation a été demandée avec
      "cancel()" et la coroutine encapsulée a propagé l'exception
      "CancelledError" qui a été levée en son sein.

   uncancel()

      Décrémente le nombre de demandes d'annulation pour cette tâche.

      Renvoie le nombre restant de demandes d'annulation.

      Notez qu'une fois l'exécution d'une tâche annulée terminée, les
      appels ultérieurs à "uncancel()" ne font rien.

      Ajouté dans la version 3.11.

      Cette méthode est utilisée par les composants internes
      d'*asyncio* et elle ne devrait pas être utilisée par le code de
      l'utilisateur final. En particulier, si une tâche est annulée
      avec succès, cela permet aux structures permettant le multi-fils
      tels que Groupes de tâches et "asyncio.timeout()" de continuer à
      s'exécuter, isolant l'annulation au bloc concerné. Par exemple :

         async def make_request_with_timeout():
             try:
                 async with asyncio.timeout(1):
                     # Structured block affected by the timeout:
                     await make_request()
                     await make_another_request()
             except TimeoutError:
                 log("There was a timeout")
             # Outer code not affected by the timeout:
             await unrelated_code()

      Alors que le bloc avec "make_request()" et
      "make_another_request()" peut être annulé en raison du délai
      d'attente, "unrelated_code()" devrait continuer à s'exécuter
      même en cas d'atteinte du délai maximal. Ceci est implémenté
      avec "uncancel()". Les gestionnaires de contexte "TaskGroup"
      utilisent "uncancel()" de la même manière.

      If end-user code is, for some reason, suppressing cancellation
      by catching "CancelledError", it needs to call this method to
      remove the cancellation state.

      When this method decrements the cancellation count to zero, the
      method checks if a previous "cancel()" call had arranged for
      "CancelledError" to be thrown into the task. If it hasn't been
      thrown yet, that arrangement will be rescinded (by resetting the
      internal "_must_cancel" flag).

   Modifié dans la version 3.13: Changed to rescind pending
   cancellation requests upon reaching zero.

   cancelling()

      Renvoie le nombre de demandes d'annulation en attente à cette
      tâche, c'est-à-dire le nombre d'appels à "cancel()" moins le
      nombre d'appels à "uncancel()".

      Notez que si ce nombre est supérieur à zéro mais que la tâche
      est toujours en cours d'exécution, "cancelled()" renvoie
      toujours "False". En effet, ce nombre peut être réduit en
      appelant "uncancel()", ce qui peut empêcher en fin de compte la
      tâche d'être annulée si les demandes d'annulation tombent à
      zéro.

      Cette méthode est utilisée par les composants internes
      d'*asyncio* et ne devrait pas être utilisée par le code de
      l'utilisateur final. Voir "uncancel()" pour plus de détails.

      Ajouté dans la version 3.11.
