8.4. Gather

Capitolul anterior a arătat cum asyncio.create_task() programează o corutină și nu o așteaptă. Operațiunea complementară – rulează mai multe obiecte awaitable concurent și așteaptă-le pe toate – este asyncio.gather().

8.4.1. Forma de bază

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

Ieșire:

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

Două lucruri de remarcat. Mai întâi, lista de rezultate este în ordinea argumentelor către gather, nu în ordinea în care corutinele s-au încheiat – "c" a returnat prima, dar este totuși a treia intrare. În al doilea rând, apelul a durat 200 ms în total, nu 350 ms: cele trei pauze au rulat concurent, iar gather() returnează de îndată ce cea mai lentă se încheie.

Ambele fapte provin din aceeași sursă. gather() încapsulează fiecare argument care nu este deja o sarcină (corutinele din exemplu), le programează pe buclă, suspendă corutina apelantă până când toate se încheie, apoi returnează rezultatele lor în ordinea originală.

8.4.2. Când să recurgi la el

Oriunde aplicația are N operațiuni awaitable și dorește rezultatele tuturor înainte de a continua. Exemple tipice:

  • Emiterea mai multor cereri de rețea în paralel și așteptarea tuturor răspunsurilor.

  • Citirea de la mai mulți senzori concurent înainte de a procesa rezultatul combinat.

  • Unirea mai multor sarcini auxiliare de scurtă durată la sfârșitul unei faze a aplicației.

Nu este instrumentul potrivit pentru sarcinile de fundal de lungă durată pe care aplicația le pornește și le lasă să ruleze pe durata de viață a programului – acelea rămân treaba lui create_task(). gather() este pentru tiparul fan-out / fan-in: împarte munca, execut-o concurent, reunește.

8.4.3. Excepții în grup

Dacă oricare dintre obiectele awaitable adunate ridică o excepție, comportamentul implicit este de a reridica excepția în afara apelului gather. Frații care nu s-au încheiat încă sunt anulați în fundal.

Acesta este de obicei ceea ce dorește o aplicație – una dintre cele N sarcini paralele a eșuat, deci operațiunea combinată a eșuat, deci oprește-te din a pierde timp cu restul. Uneori aplicația dorește opusul: să lași fiecare obiect awaitable să se încheie (sau să eșueze) independent și să inspectezi rezultatele după aceea. Pasează return_exceptions=True pentru asta:

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

Fiecare intrare din lista returnată este acum fie o valoare de retur normală, fie excepția pe care a ridicat-o obiectul awaitable corespunzător. Apelantul verifică isinstance(r, Exception) pentru a le distinge.

8.4.4. Anulare

Anularea gather-ului în sine – prin anularea sarcinii care îl aștepta – anulează fiecare obiect awaitable care încă rulează în interiorul lui. Pagina expirări și anulare tratează în detaliu cum se propagă anularea prin lanțul de apeluri.