8.4. Gather

Prethodno poglavlje pokazalo je kako asyncio.create_task() raspoređuje korutinu i ne čeka na nju. Pripadajuća operacija – izvrši nekoliko awaitable objekata istovremeno i pričekaj sve njih – jest asyncio.gather().

8.4.1. Osnovni oblik

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

Izlaz:

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

Dvije stvari treba primijetiti. Prvo, popis rezultata je u redoslijedu argumenata za gather, a ne u redoslijedu kojim su korutine završile – "c" je vratila prva, ali je i dalje treći unos. Drugo, poziv je trajao ukupno 200 ms, a ne 350 ms: tri spavanja izvršila su se istovremeno, a gather() vraća čim najsporije od njih završi.

Obje činjenice proizlaze iz istog izvora. gather() omata svaki argument koji već nije zadatak (korutine u primjeru), raspoređuje ih na petlji, obustavlja pozivajuću korutinu sve dok sve one ne završe, zatim vraća njihove rezultate u izvornom redoslijedu.

8.4.2. Kada posegnuti za njim

Svugdje gdje aplikacija ima N awaitable operacija i želi rezultate svih njih prije nego što nastavi. Tipični primjeri:

  • Izdavanje nekoliko mrežnih zahtjeva paralelno i čekanje na sve odgovore.

  • Čitanje s nekoliko senzora istovremeno prije obrade kombiniranog rezultata.

  • Spajanje nekoliko kratkotrajnih pomoćnih zadataka na kraju faze aplikacije.

Nije pravi alat za dugotrajne pozadinske zadatke koje aplikacija pokreće i ostavlja da rade tijekom cijelog života programa – to je i dalje posao za create_task(). gather() je za obrazac fan-out / fan-in: razdijeli posao, obavi ga istovremeno, ponovno spoji.

8.4.3. Iznimke u grupi

Ako bilo koji od okupljenih awaitable objekata izazove iznimku, zadano ponašanje je ponovno izazivanje iznimke izvan poziva gathera. Srodni objekti koji još nisu završili otkazuju se u pozadini.

To je obično ono što aplikacija želi – jedan od N paralelnih poslova nije uspio, pa kombinirana operacija nije uspjela, pa prestani trošiti vrijeme na ostale. Ponekad aplikacija želi suprotno: pustiti svaki awaitable da završi (ili padne) neovisno, a rezultate pregledati nakon toga. Za to proslijedite return_exceptions=True

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

Svaki unos u vraćenom popisu sada je ili normalna povratna vrijednost ili iznimka koju je odgovarajući awaitable izazvao. Pozivatelj provjerava isinstance(r, Exception) kako bi ih razlikovao.

8.4.4. Otkazivanje

Otkazivanje samog gathera – otkazivanjem onog zadatka koji je čekao na njega – otkazuje svaki awaitable koji se još uvijek izvršava unutar njega. Stranica istek vremena i otkazivanje detaljno pokriva kako se otkazivanje propagira kroz lanac poziva.