8.4. Gather

Bab sebelumnya menunjukkan bagaimana asyncio.create_task() menjadwalkan coroutine dan tidak menunggunya. Operasi pendampingnya -- jalankan beberapa awaitable secara bersamaan dan tunggu semuanya -- adalah asyncio.gather().

8.4.1. Bentuk dasarnya

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

Keluaran:

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

Dua hal yang perlu diperhatikan. Pertama, daftar hasil sesuai urutan argumen gather, bukan urutan coroutine selesai -- "c" selesai lebih dulu tetapi tetap menjadi entri ketiga. Kedua, panggilan memakan waktu 200 ms secara total, bukan 350 ms: ketiga sleep berjalan secara bersamaan, dan gather() kembali segera setelah yang paling lambat selesai.

Kedua fakta berasal dari sumber yang sama. gather() membungkus setiap argumen yang belum menjadi task (coroutine dalam contoh), menjadwalkannya di loop, menangguhkan coroutine pemanggil hingga semua selesai, kemudian mengembalikan hasilnya dalam urutan asli.

8.4.2. Kapan menggunakannya

Di mana pun aplikasi memiliki N operasi awaitable dan menginginkan hasil semua sebelum melanjutkan. Contoh umum:

  • Mengirimkan beberapa permintaan jaringan secara paralel dan menunggu semua respons.

  • Membaca dari beberapa sensor secara bersamaan sebelum memproses hasil gabungan.

  • Menggabungkan beberapa task helper berumur pendek di akhir fase aplikasi.

Ini bukan alat yang tepat untuk task latar belakang yang berjalan lama yang dimulai aplikasi dan dibiarkan berjalan selama masa pakai program -- itu masih menggunakan create_task(). gather() adalah untuk pola fan-out / fan-in: bagi pekerjaan, lakukan secara bersamaan, gabungkan kembali.

8.4.3. Eksepsi dalam grup

Jika salah satu awaitable yang di-gather memunculkan eksepsi, perilaku default adalah memunculkan kembali eksepsi tersebut keluar dari panggilan gather. Saudara-saudara yang belum selesai dibatalkan di latar belakang.

Biasanya itulah yang diinginkan aplikasi -- salah satu dari N pekerjaan paralel gagal, maka operasi gabungan telah gagal, jadi hentikan pemrosesan sisanya. Terkadang aplikasi menginginkan sebaliknya: biarkan setiap awaitable selesai (atau gagal) secara independen, dan periksa hasilnya setelahnya. Berikan return_exceptions=True untuk itu:

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

Setiap entri dalam daftar yang dikembalikan sekarang berupa nilai kembalian normal atau eksepsi yang di-raise awaitable yang sesuai. Pemanggil memeriksa isinstance(r, Exception) untuk membedakannya.

8.4.4. Pembatalan

Membatalkan gather itu sendiri -- dengan membatalkan task apa pun yang sedang await-nya -- membatalkan setiap awaitable yang masih berjalan di dalamnya. Halaman timeout dan pembatalan membahas cara pembatalan merambat melalui rantai pemanggilan secara rinci.