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í.