0%

Een voorproefje van async

Wat dit voorproefje weglaat

Dit hoofdstuk beloofde een voorproefje, en hier houdt het op. Je kunt nu een async def-handler lezen en elke plek vinden waar hij de controle teruggeeft: elke await. Je hebt een gedraaid met asyncio.run, op de server, en de kijkershandler van het scorebord geschreven als coroutine die next_snapshot(), send en close awaitet. En je hebt de vorm van een websockets.asyncio-server gelezen. Deze les noemt wat een echte cursus in asyncio zou toevoegen, zodat je weet waar je kennis ophoudt, en leest daarna nog de versie van één framework van de handler die je schreef.

Tasks en annulering

In de Run van les 3 werden drie kijkers tegelijk bediend op één thread. De asyncio-server deed dat door de handler van elke verbinding als te draaien: een coroutine die de zelfstandig in leven houdt en naast de andere draait, met een wissel bij elke await. Je hebt zelf nooit een task gestart. next_snapshot() kwam kant-en-klaar, en de server startte voor elke handler een task.

Echte async-programma’s starten voortdurend eigen tasks: een handler die op de volgende snapshot wacht en tegelijk zijn verbinding in de gaten houdt, een server die een scorebijhouder en een klok naast elkaar draait, een client die met drie servers tegelijk praat. Een task starten, wachten tot er meerdere klaar zijn (een asyncio.TaskGroup is het standaardhulpmiddel), en ze netjes stoppen, door een task te annuleren en hem te laten opruimen als hem gezegd wordt te stoppen, zijn vaardigheden met eigen regels. Ze worden hier genoemd en niet behandeld. Ze vormen het hart van de cursus Async Python, die alleen Python in Practice nodig heeft.

Welke hiervan is een onderwerp voor de cursus Async Python in plaats van een onderwerp dat deze cursus behandelde?

Blokkerende aanroepen binnen een lus

Les 3 hield time.sleep en Event.wait uit de handler, omdat een aanroep die blokkeert de enige thread van de lus bezet houdt. Die twee zijn makkelijk te zien. Andere verstoppen zich beter: een recv op een gewone , een groot bestand lezen, een lange berekening, of elke bibliotheekfunctie die zonder await op een netwerk of een schijf wacht. Binnen een coroutine houdt elk daarvan elke andere kijker tegen tot hij terugkeert.

Er zijn twee gebruikelijke oplossingen. Gebruik een bibliotheek die voor de lus is gebouwd, zoals websockets.asyncio de synchrone server vervangt die je eerder gebruikte. Of geef de blokkerende aanroep aan een thread, zodat de lus door kan terwijl hij wacht; asyncio heeft daar een voor, asyncio.to_thread. Weten welke aanroepen blokkeren, en kiezen tussen die oplossingen, is weer het onderwerp van Async Python.

Welke regel binnen een async def-handler houdt elke andere kijker tegen terwijl hij draait?

Async-code testen

Toen je in les 3 op Submit klikte, stapte de controle met de hand door je coroutine, met stand-ins die op gelabelde punten de controle teruggaven en helemaal zonder . Dat laat precies zien waar een handler awaitet, en het is genoeg voor een handler met drie awaits. Echte projecten testen coroutines op een echte lus: met een plug-in voor pytest die async def-testfuncties draait, of met asyncio.run binnen een gewone test, en ze testen ook time-outs en . Async Python is daar ook de plek voor.

FastAPI, nog één keer

In with Python maakte hoofdstuk 11 van je handler -path-operations, en las je daar decorators zoals @app.get(...). FastAPI heeft ook -endpoints, en dat zijn coroutines zoals degene die je schreef. Hier is een kleine, met een test binnen het proces, alleen om te lezen:

from fastapi import FastAPI, WebSocket
from fastapi.testclient import TestClient

app = FastAPI()


@app.websocket("/watch")
async def watch(websocket: WebSocket):
    await websocket.accept()
    await websocket.send_json({"type": "snapshot", "version": 0, "scores": {"Owls": 0}})
    await websocket.close(code=1000, reason="the quiz is over")


with TestClient(app).websocket_connect("/watch") as ws:
    print(ws.receive_json())
    ws.receive_json()

Lees hem naast je handler. @app.websocket("/watch") registreert de coroutine voor WebSocket- op /watch, zoals @app.get een functie registreerde voor GET-verzoeken. De annotatie in websocket: WebSocket, ook alleen om te lezen, is hoe FastAPI weet dat het de verbinding moet meegeven. await websocket.accept() is de aanroep die de server de upgrade laat beantwoorden met 101 Switching Protocols; tot dan kon het verzoek nog geweigerd worden, zoals je code uit hoofdstuk 2 het weigerde. send_json codeert de en stuurt hem als tekstbericht, en close stuurt het close- met je code en reden. De test gebruikt de TestClient die je kent: websocket_connect voert de handshake uit in hetzelfde proces, zonder en zonder netwerk.

De voorbeelden van deze les draaien in je browser, waar FastAPI niet beschikbaar is, dus deze code draait hier niet. De serversandbox van de site heeft FastAPI en zijn testclient wel, en daar ontving de test de snapshot, {'type': 'snapshot', 'version': 0, 'scores': {'Owls': 0}}, en wierp de tweede receive_json() WebSocketDisconnect op met code 1000 en de reden the quiz is over.

Waarmee komt await websocket.accept() in het FastAPI-endpoint overeen in je code uit hoofdstuk 2?

Dat is de grens van het voorproefje. Het volgende hoofdstuk maakt van deze grenzen routes waaruit je kunt kiezen.