8.4. Gather

Předchozí kapitola ukázala, jak asyncio.create_task() naplánuje korutinu a nečeká na ni. Doplňková operace – spustit několik awaitable objektů souběžně a počkat na všechny – je asyncio.gather().

8.4.1. Základní podoba

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

Výstup:

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

Všimněte si dvou věcí. Za prvé, seznam výsledků je v pořadí argumentů funkce gather, nikoli v pořadí, v jakém korutiny skončily – "c" se vrátila první, ale je stále třetí položkou. Za druhé, volání trvalo celkem 200 ms, nikoli 350 ms: tři spánky proběhly souběžně a gather() se vrátí, jakmile dokončí nejpomalejší z nich.

Obě skutečnosti pocházejí ze stejného zdroje. gather() obalí každý argument, který ještě není úlohou (korutiny v příkladu), naplánuje je na smyčku, pozastaví volající korutinu, dokud všechny neskončí, a poté vrátí jejich výsledky v původním pořadí.

8.4.2. Kdy po ní sáhnout

Všude tam, kde má aplikace N awaitable operací a chce výsledky všech z nich, než bude pokračovat. Typické příklady:

  • Vyslání několika síťových požadavků paralelně a čekání na všechny odpovědi.

  • Čtení z několika senzorů souběžně před zpracováním kombinovaného výsledku.

  • Spojení několika krátce žijících pomocných úloh na konci fáze aplikace.

Není to správný nástroj pro dlouho běžící úlohy na pozadí, které aplikace spustí a nechá běžet po celou dobu života programu – ty jsou stále záležitostí create_task(). gather() slouží pro vzor fan-out / fan-in: rozdělit práci, vykonat ji souběžně, znovu spojit.

8.4.3. Výjimky ve skupině

Pokud kterýkoli ze shromážděných awaitable objektů vyvolá výjimku, výchozím chováním je znovu vyvolat výjimku ven z volání gather. Sourozenci, kteří ještě neskončili, jsou na pozadí zrušeni.

To je obvykle to, co aplikace chce – jedna z N paralelních úloh selhala, takže kombinovaná operace selhala, a tudíž přestaňte trávit čas na zbytku. Někdy aplikace chce opak: nechat každý awaitable objekt dokončit (nebo selhat) nezávisle a výsledky prozkoumat až poté. K tomu předejte return_exceptions=True

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

Každá položka ve vráceném seznamu je nyní buď normální návratová hodnota, nebo výjimka, kterou odpovídající awaitable objekt vyvolal. Volající pomocí isinstance(r, Exception) zjistí, o které se jedná.

8.4.4. Rušení

Zrušení samotného gather – zrušením úlohy, která na něj čekala – zruší každý awaitable objekt, který v něm stále běží. Stránka timeouty a rušení podrobně popisuje, jak se rušení propaguje řetězcem volání.