15. Programmation Asynchrone

async/await, asyncio, coroutines, tasks, gather(), timeouts, aiohttp.


15.1 Coroutines

Fonction définie avec async def qui retourne un objet coroutine :

async def dire_bonjour():
    print("Bonjour")
    await asyncio.sleep(1)  # cède le contrôle
    print("Au revoir")

15.2 Exécuter une coroutine

import asyncio
 
async def main():
    print("Début")
    await dire_bonjour()
    print("Fin")
 
asyncio.run(main())

asyncio.run() crée la boucle d’événements, exécute main(), et nettoie.

15.3 await

Suspend la coroutine courante jusqu’à ce que l’awaitable soit terminé.

async def fetch_data():
    await asyncio.sleep(2)
    return {"data": 42}
 
async def main():
    resultat = await fetch_data()
    print(resultat)

15.4 Tâches concurrentes

async def main():
    # Créer des tâches (elles s'exécutent en concurrence)
    t1 = asyncio.create_task(fetch_data())
    t2 = asyncio.create_task(fetch_data())
 
    # Attendre les deux
    r1, r2 = await asyncio.gather(t1, t2)
    print(r1, r2)

asyncio.gather() — lance toutes les tâches en concurrence :

results = await asyncio.gather(
    fetch_data(1),
    fetch_data(2),
    fetch_data(3),
)

15.5 Timeout

async def main():
    try:
        resultat = await asyncio.wait_for(
            fetch_data(),
            timeout=1.0  # lève asyncio.TimeoutError après 1s
        )
    except asyncio.TimeoutError:
        print("Trop long !")

15.6 asyncio.sleep()

Remplacement non-bloquant de time.sleep() :

await asyncio.sleep(0.1)  # 100 ms sans bloquer le thread

15.7 Boucle d’événements avancée

async def main():
    loop = asyncio.get_running_loop()
    # call_soon, run_in_executor (pour bloquant)
    resultat = await loop.run_in_executor(
        None,  # thread pool par défaut
        bloquant_function, arg1, arg2
    )

15.8 Exemple : téléchargements concurrents

import asyncio
import aiohttp
 
async def télécharger(url: str) -> str:
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()
 
async def main():
    urls = [
        "https://example.com",
        "https://python.org",
        "https://docs.python.org",
    ]
    pages = await asyncio.gather(*[télécharger(u) for u in urls])
    for url, contenu in zip(urls, pages):
        print(f"{url}: {len(contenu)} caractères")

15.9 async for et async with

# Itération asynchrone
async for chunk in stream():
    traiter(chunk)
 
# Gestionnaire de contexte asynchrone
async with aiohttp.ClientSession() as session:
    ...

15.10 Structures de données asynchrones

from asyncio import Queue, Semaphore
 
queue = asyncio.Queue()
await queue.put(item)
item = await queue.get()

15.11 Bonnes pratiques

  • Ne pas mélanger I/O bloquant et async (utiliser run_in_executor).
  • Éviter asyncio.run() dans une boucle déjà en cours.
  • Utiliser asyncio.gather() pour la concurrence, pas des boucles séquentielles.
  • Toujours gérer les timeouts pour les I/O réseau.

🔗 ← Retour au cours · ← précédent · Suivant →