8.4. Gather

Det föregående kapitlet visade hur asyncio.create_task() schemalägger en korutin och inte väntar på den. Motsvarande operation – kör flera väntbara samtidigt och vänta på alla – är asyncio.gather().

8.4.1. Grundformen

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())

Utdata:

['a', 'b', 'c']

Två saker att lägga märke till. Först, resultatlistan följer ordningen för argumenten till gather, inte den ordning i vilken korutinerna avslutades – "c" returnerade först men är fortfarande den tredje posten. För det andra tog anropet 200 ms totalt, inte 350 ms: de tre sovperioderna kördes samtidigt, och gather() returnerar så snart den långsammaste slutförs.

Båda fakta kommer från samma källa. gather() omsluter varje argument som inte redan är en uppgift (korutinerna i exemplet), schemalägger dem på loopen, pausar den anropande korutinen tills alla har slutförts och returnerar sedan deras resultat i den ursprungliga ordningen.

8.4.2. När man ska ta till den

Överallt där applikationen har N väntbara operationer och vill ha resultaten av alla innan den fortsätter. Typiska exempel:

  • Skicka flera nätverksförfrågningar parallellt och vänta på alla svar.

  • Läsa från flera sensorer samtidigt innan det kombinerade resultatet bearbetas.

  • Sammanfoga flera kortlivade hjälpuppgifter i slutet av en applikationsfas.

Det är inte rätt verktyg för långkörande bakgrundsuppgifter som applikationen startar och låter köra under programmets livstid – de är fortfarande ett jobb för create_task(). gather() är till för mönstret fan-out / fan-in: dela upp arbete, gör det samtidigt, sammanfoga.

8.4.3. Undantag i gruppen

Om någon av de samlade väntbara kastar ett undantag är standardbeteendet att kasta undantaget på nytt ut ur gather-anropet. Syskonen som ännu inte har slutförts avbryts i bakgrunden.

Det är vanligtvis vad en applikation vill – ett av N parallella jobb misslyckades, så den kombinerade operationen har misslyckats, alltså sluta lägga tid på resten. Ibland vill applikationen ha det motsatta: låt varje väntbar slutföras (eller misslyckas) oberoende, och inspektera resultaten efteråt. Skicka return_exceptions=True för det:

results = await asyncio.gather(
    fetch_or_fail("ok"),
    fetch_or_fail("bad"),
    return_exceptions=True,
)
# results == ["ok-value", OSError(...)]

Varje post i den returnerade listan är nu antingen ett normalt returvärde eller undantaget som motsvarande väntbar kastade. Anroparen kontrollerar isinstance(r, Exception) för att skilja dem åt.

8.4.4. Avbrytning

Att avbryta själva gather – genom att avbryta vilken uppgift som än väntade på den – avbryter varje väntbar som fortfarande körs inuti den. Sidan timeouts och avbrytning täcker i detalj hur avbrytning propagerar genom anropskedjan.