Asynchronous en Python

Publié le 26 août 2025

Au début du développement d’un programme, automatiser l’exécution de tâches par lots semble toujours plus efficace que de les déclencher manuellement. Mais lorsque les projets atteignent une certaine maturité, les besoins de **rapidité** se font ressentir. C’est ce que j’ai expérimenté récemment en développant un programme de **market making sur options** : l’exécution devient alors critique !

Par défaut, un programme Python est exécuté de façon **linéaire** : chaque étape doit se terminer avant que la suivante ne commence. Mais si certaines tâches sont indépendantes, comment augmenter l’efficacité ?

Prenons un exemple du quotidien — la cuisine 🍳. En tant que débutant à l’étranger, je devais apprendre à cuisiner moi-même. Et j’ai vite compris que je ne pouvais pas tout faire en même temps sans m’embrouiller. Cela illustre parfaitement les différents modes d’exécution en programmation.

Lv 1 Cuisinier débutant : (Single thread)

Lv 2 Cuisinier intermédiaire : (Asynchronous)

Lv 3 Cuisinier confirmé : (Multi-threads + Async)

😅 Ce sont littéralement les leçons que j’ai tirées en cuisinant !

En Python, appliquons ce raisonnement : préparer du café (3 minutes) et griller un bagel (5 minutes). Si on les exécute séquentiellement, cela prend 8 minutes. Mais en parallèle, on peut optimiser !

Exemple classique (sans Async)


import time

def brew_coffee():
    print("Start brew coffee")
    time.sleep(3 * 60)
    print("End brew coffee")
    return "coffee ready"
    
def toast_bagel():
    print("Start toast bagel")
    time.sleep(5 * 60)
    print("End toast bagel")
    return "bagel toasted"
    
def main():
    start_time = time.time()
    
    result_coffee = brew_coffee()
    result_bagel = toast_bagel()
    
    end_time = time.time()
    elapsed_time = end_time - start_time
    
    print(f"Result of brew_coffee: {result_coffee}")
    print(f"Result of toast bagel: {result_bagel}")
    print(f"Total execution time: {elapsed_time:2f} seconds")

if __name__ == "__main__":
    main()
    

La version suivante illustre l’approche **asynchrone** : pendant que la machine à café travaille, on lance le grille-pain, et les deux se terminent quasiment en même temps ⏱️.

Exemple avec Async


import asyncio
import time

async def brew_coffee():
    print("Start brew coffee")
    await asyncio.sleep(3 * 60)
    print("End brew coffee")
    return "coffee ready"
    
async def toast_bagel():
    print("Start toast bagel")
    await asyncio.sleep(5 * 60)
    print("End toast bagel")
    return "bagel toasted"
    
async def main():
    start_time = time.time()
    
    # méthode 1: asyncio.gather()
    batch = asyncio.gather(brew_coffee(), toast_bagel())
    result_coffee, result_bagel = await batch
    
    # méthode 2: asyncio.create_task()
    # coffee_task = asyncio.create_task(brew_coffee())
    # toast_task = asyncio.create_task(toast_bagel())
    # result_coffee = await coffee_task
    # result_toast = await toast_task
    
    end_time = time.time()
    elapsed_time = end_time - start_time
    
    print(f"Result of brew_coffee: {result_coffee}")
    print(f"Result of toast bagel: {result_bagel}")
    print(f"Total execution time: {elapsed_time:2f} seconds")
    
if __name__ == "__main__":
    asyncio.run(main())
    

Conclusion

L’asynchronisme est aujourd’hui une méthode courante pour accélérer des tâches gourmandes en temps, comme les opérations **I/O** (base de données, API, web scraping). La première étape est d’identifier quelles parties du projet sont lentes et peuvent être parallélisées.

D’autres solutions comme multiprocessing et threading existent pour le traitement parallèle, mais elles consomment davantage de ressources CPU/mémoire et peuvent entraîner des blocages si elles sont mal utilisées. Pour ma part, j’utilise souvent une combinaison des deux approches.

Références