8.4. Gather

Az előző fejezet bemutatta, hogyan ütemez az asyncio.create_task() egy korutint, és hogyan nem vár rá. A társművelet – több várhatót egyidejűleg futtatni és mindegyikre megvárni – az asyncio.gather().

8.4.1. Az alapforma

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

Kimenet:

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

Két dolog figyelhető meg. Először is, az eredménylista a gather argumentumainak sorrendjében van, nem abban a sorrendben, ahogy a korutinok befejeződtek – a "c" tért vissza először, mégis ez a harmadik bejegyzés. Másodszor, a hívás összesen 200 ms ideig tartott, nem 350 ms ideig: a három alvás egyidejűleg futott, és az gather() azonnal visszatér, amint a leglassabb befejeződik.

Mindkét tény ugyanabból a forrásból ered. Az gather() minden olyan argumentumot, amely még nem feladat (a példában a korutinokat), beburkol, ütemezi őket a hurkon, felfüggeszti a hívó korutint, amíg mindegyik be nem fejeződik, majd visszaadja az eredményeiket az eredeti sorrendben.

8.4.2. Mikor érdemes nyúlni hozzá

Bárhol, ahol az alkalmazásnak N darab várható művelete van, és mindegyik eredményét meg akarja kapni, mielőtt folytatja. Tipikus példák:

  • Több hálózati kérés párhuzamos kiküldése és az összes válasz megvárása.

  • Több érzékelő egyidejű olvasása, mielőtt feldolgozná a kombinált eredményt.

  • Több rövid életű segédfeladat összevonása egy alkalmazásfázis végén.

Nem ez a megfelelő eszköz a hosszan futó háttérfeladatokhoz, amelyeket az alkalmazás elindít és a program teljes élettartamára futni hagy – azok továbbra is create_task() munka. Az gather() a szétterítés / összegyűjtés mintára való: munkát felosztani, egyidejűleg elvégezni, újraegyesíteni.

8.4.3. Kivételek a csoportban

Ha az összegyűjtött várhatók közül bármelyik kivételt vált ki, az alapértelmezett viselkedés az, hogy a kivételt újra kiváltja a gather hívásból. A még be nem fejezett testvéreket a háttérben megszakítja.

Ez általában az, amit egy alkalmazás szeretne – az N párhuzamos feladat egyike meghiúsult, így a kombinált művelet meghiúsult, ezért ne pazaroljunk időt a többire. Néha az alkalmazás az ellenkezőjét akarja: hagyni, hogy minden várható függetlenül befejeződjön (vagy meghiúsuljon), és utólag megvizsgálni az eredményeket. Ehhez add át a return_exceptions=True paramétert:

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

A visszaadott lista minden egyes bejegyzése most vagy egy normál visszatérési érték, vagy a kivétel, amelyet a megfelelő várható kiváltott. A hívó az isinstance(r, Exception) ellenőrzéssel különbözteti meg őket.

8.4.4. Megszakítás

Magának a gathernek a megszakítása – bármely feladat megszakításával, amely megvárta – megszakít minden benne még futó várhatót. A időtúllépések és megszakítás oldal részletesen tárgyalja, hogyan terjed a megszakítás a hívási láncon.