À l'issue de cette section, vous aurez découvert

Mise en place du TP

Pour ce TP, utilisez la branche suivante :

git checkout 6_starting_async_sync

Explorer le code mis à disposition

Aller explorer le fichier async_sync_api.py, dedans 3 routes d'api sont définies :

Le paramètre n est là pour illustrer une complexité métier de l'appel, plus n est grand plus l'appel est long.

Le sleep est un moyen de modéliser facilement un long calcul.

Lancer l'api

uv run  fastapi dev demos/async_sync_api.py 

Et tester avec un curl

curl localhost:8000/blocking/1

Comparer les délais de l'API blocking

Pour cela, nous allons utiliser deux terminaux : 1 pour chaque client :

Lancer les commandes :

curl localhost:8000/blocking/15
time curl localhost:8000/blocking/1

time permet de mesurer le temps mis.

Combien de temps met le premier curl, et le deuxième ?
Que se passe-t-il si on inverse l'ordre des curls ?

Comparer les temps de l'API non-blocking

Lancer les commandes :

curl localhost:8000/nonblocking/15
time curl localhost:8000/nonblocking/1

time permet de mesurer le temps mis.

Combien de temps met le premier curl, et le deuxième ?
Que se passe-t-il si on inverse l'ordre des curls ?

Comparer les temps de l'API sync

Lancer les commandes :

curl localhost:8000/sync/15
time curl localhost:8000/sync/1

time permet de mesurer le temps mis.

Combien de temps met le premier curl, et le deuxième ?

Il y a une parallélisation qui est faite au niveau des threads et non plus dans la boucle asyncio.

Réduction du nombre de threads

Essayons de réduire le nombre de threads pour illustrer cette différence.

Remplacer

app = FastAPI()

Par ce code qui passe de la valeur par défaut (40 threads) à 1 thread.

@asynccontextmanager
async def lifespan(app: FastAPI):
    anyio.to_thread.current_default_thread_limiter().total_tokens = 1
    yield


app = FastAPI(lifespan=lifespan)

Relancer les temps sur les méthodes de sync et non-blocking.

Schéma d'illustration

Le schéma ci-dessous montre, pour un worker uvicorn, qui exécute chaque type de route :

flowchart TB
    Client["Client (curl)"]

    subgraph W1["Worker uvicorn"]
        Route1["/blocking, /nonblocking<br/>(async def)"]
        Route2["/sync<br/>(def)"]

        Route1 -->|"exécuté directement par"| EL1["Event loop asyncio<br/>1 seul thread"]
        Route2 -->|"délégué à"| TP1

        subgraph TP1["Threadpool"]
            direction LR
            T1a["Thread 1"]
            T1b["Thread 2"]
            T1c["Thread N"]
        end
    end

    Client --> W1

Points clés à retenir :

Les instructions du tp suivant sont ici