8.4. Gather¶
Het vorige hoofdstuk liet zien hoe asyncio.create_task() een coroutine inplant en er niet op wacht. De bijbehorende bewerking – meerdere awaitables gelijktijdig draaien en op allemaal wachten – is asyncio.gather().
8.4.1. De basisvorm¶
import asyncio
async def fetch(name, delay_ms):
await asyncio.sleep_ms(delay_ms)
return name
async def main():
results = await asyncio.gather(
fetch("a", 100),
fetch("b", 200),
fetch("c", 50),
)
print(results)
asyncio.run(main())
Uitvoer:
['a', 'b', 'c']
Twee dingen om op te merken. Ten eerste staat de resultatenlijst in de volgorde van de argumenten aan gather, niet in de volgorde waarin de coroutines klaar waren – "c" keerde als eerste terug maar is nog steeds de derde vermelding. Ten tweede duurde de aanroep in totaal 200 ms, niet 350 ms: de drie slaapperiodes draaiden gelijktijdig, en gather() keert terug zodra de traagste is voltooid.
Beide feiten komen uit dezelfde bron. gather() wikkelt elk argument dat nog geen taak is (de coroutines in het voorbeeld) in, plant ze in op de loop, schorst de aanroepende coroutine op totdat allemaal klaar zijn, en retourneert dan hun resultaten in de oorspronkelijke volgorde.
8.4.2. Wanneer je het moet gebruiken¶
Overal waar de applicatie N awaitbare bewerkingen heeft en de resultaten van allemaal wil voordat het verdergaat. Typische voorbeelden:
Meerdere netwerkverzoeken parallel uitsturen en op alle antwoorden wachten.
Gelijktijdig van meerdere sensoren lezen voordat het gecombineerde resultaat wordt verwerkt.
Meerdere kortlevende helpertaken samenvoegen aan het einde van een applicatiefase.
Het is niet het juiste hulpmiddel voor langlopende achtergrondtaken die de applicatie start en de hele levensduur van het programma laat draaien – die zijn nog steeds werk voor create_task(). gather() is voor het fan-out / fan-in-patroon: splits werk op, doe het gelijktijdig, voeg het weer samen.
8.4.3. Excepties in de groep¶
Als een van de verzamelde awaitables een exceptie opwerpt, is het standaardgedrag om de exceptie opnieuw op te werpen uit de gather-aanroep. De zusters die nog niet klaar zijn, worden op de achtergrond geannuleerd.
Dat is meestal wat een applicatie wil – een van de N parallelle taken is mislukt, dus de gecombineerde bewerking is mislukt, dus stop met tijd besteden aan de rest. Soms wil de applicatie het tegenovergestelde: laat elke awaitable onafhankelijk eindigen (of mislukken), en inspecteer de resultaten naderhand. Geef daarvoor return_exceptions=True mee:
results = await asyncio.gather(
fetch_or_fail("ok"),
fetch_or_fail("bad"),
return_exceptions=True,
)
# results == ["ok-value", OSError(...)]
Elke vermelding in de geretourneerde lijst is nu ofwel een normale retourwaarde of de exceptie die de bijbehorende awaitable opwierp. De aanroeper controleert isinstance(r, Exception) om ze van elkaar te onderscheiden.
8.4.4. Annulering¶
Het annuleren van de gather zelf – door de taak te annuleren die erop wachtte – annuleert elke awaitable die er nog binnen draait. De pagina timeouts en annulering behandelt in detail hoe annulering door de aanroepketen propageert.