Topik temu duga representatif

Temuduga Pengekodan: Bagaimana Anda Mengurus Kegagalan Serentak dengan Python asyncio.TaskGroup?

PengekodanSukar
Pasukan Editorial Offer.ccDiterbitkan Dikemas kini

Soalan

Gunakan Python asyncio.TaskGroup untuk memuatkan data pengguna, pesanan dan pengesyoran secara serentak. Batalkan tugas adik-beradik (sibling tasks) apabila salah satu gagal, kekalkan ralat yang boleh didiagnosis, dan bersihkan sumber selepas pembatalan pemanggil atau tamat masa. Terangkan perbezaannya daripada asyncio.gather dan kerja mana yang tidak sepatutnya berada dalam skop permintaan.

Gesaan dan konteks

Satu API tak segerak (async API) memuatkan data pengguna, pesanan dan pengesyoran secara serentak. Kegagalan dalam tugas yang diperlukan harus membatalkan tugas adik-beradik (siblings), manakala pemutusan sambungan klien atau jumlah tamat masa keseluruhan tidak boleh meninggalkan korutin yatim. Reka bentuk pelaksanaannya dengan asyncio.TaskGroup dan terangkan pengagregatan pengecualian, pembatalan, serta pembersihan sumber.

Dokumentasi Python menerangkan TaskGroup sebagai konkurensi berstruktur: tugas-tugas selesai sebelum skop keluar; pengecualian bukan pembatalan membatalkan tugas-tugas yang selebihnya dan dibangkitkan sebagai ExceptionGroup. Temuduga ini menguji penaakulan kitaran hayat dan bukannya timbunan panggilan create_task() semata-mata.

Perkara yang dinilai oleh penemu duga

Mencari pemahaman tentang salasilah tugas induk-anak, pembatalan adik-beradik, CancelledError, ExceptionGroup, dan penggabungan async with. Calon harus menyebarkan pembatalan ke dalam pangkalan data, HTTP, dan fail, serta memindahkan kerja yang perlu kekal selepas respons ke dalam baris gilir tahan lasak (durable queue).

Soalan penjelasan

  • Adakah ketiga-tiga bacaan itu wajib, atau bolehkah pengesyoran diturunkan mutunya (degrade)?
  • Tugas mana yang membuka sambungan, kursor, atau fail sementara?
  • Siapa yang menetapkan jumlah tamat masa keseluruhan, dan bagaimana pembatalan pemanggil diterima?
  • Ralat mana yang memerlukan klasifikasi, percubaan semula (retry), atau respons yang selamat untuk pengguna?
  • Tugas mana yang mesti diteruskan selepas respons HTTP?

Jawapan 30 saat

“Cipta tiga tugas di dalam async with TaskGroup() dan tunggu sehingga skop selesai. Pengecualian bukan pembatalan membatalkan adik-beradik dan membangkitkan kumpulan pengecualian; except* mengelaskan ralat yang diketahui. Setiap tugas menutup sumber di dalam finally. Balut kumpulan tersebut dalam satu asyncio.timeout() atau pembatalan pemanggil. Kerja yang mesti kekal selepas permintaan dihantar ke baris gilir tahan lasak.”

Penyelesaian langkah demi langkah

Langkah 1: Tentukan salasilah tugas dan kontrak hasil

Isytiharkan setiap hasil dan tahap kepentingannya. Pengguna dan pesanan mungkin diperlukan manakala pengesyoran adalah pilihan; pilihan tersebut menentukan sama ada satu pengecualian akan membatalkan kumpulan itu. Setiap tugas tergolong dalam skop permintaan.

python
async with asyncio.TaskGroup() as group:
    user_task = group.create_task(load_user(user_id))
    order_task = group.create_task(load_orders(user_id))
    rec_task = group.create_task(load_recommendations(user_id))

Keluar dari skop akan menggabungkan (join) tugas-tugas tersebut. Membaca tugas yang belum selesai di luar blok bukanlah pengganti untuk menggabungkannya.

Langkah 2: Kendalikan kegagalan dengan ExceptionGroup

Pengecualian selain CancelledError membatalkan adik-beradik, dan TaskGroup membangkitkan ExceptionGroup selepas semua tugas selesai. Gunakan except* untuk ralat kebergantungan yang dijangkakan dan kekalkan ralat yang tidak diketahui untuk pengendali luar.

python
try:
    async with asyncio.TaskGroup() as group:
        user = group.create_task(load_user(user_id))
        orders = group.create_task(load_orders(user_id))
except* RetryableDependencyError as errors:
    record_dependency_failures(errors.exceptions)
    raise ServiceUnavailable from errors

Jangan tangkap BaseException dan senyap-senyap menelan pembatalan.

Langkah 3: Tolak pembatalan ke dalam I/O sebenar

TaskGroup membatalkan tugas Python; pemacu HTTP, pangkalan data, dan fail memerlukan sokongan pembatalan atau tamat masa. Setiap tugas menutup sambungan, melepaskan semafor, memadam fail sementara, dan menghentikan penggunaan di dalam finally.

python
async def load_orders(user_id: str):
    conn = await pool.acquire()
    try:
        return await conn.fetch("SELECT ...", user_id, timeout=1.5)
    finally:
        await pool.release(conn)

Jika pemacu tidak dapat mengganggu pertanyaan, gunakan tamat masa pernyataan (statement timeout), sambungan terpencil, atau tugas latar belakang yang terikat dan bukannya hanya bergantung pada pembatalan tugas semata-mata.

Langkah 4: Tetapkan satu jumlah tamat masa keseluruhan dan bezakan pembatalan luaran

Balut keseluruhan kumpulan di dalam asyncio.timeout() supaya semua kerja berkongsi satu bajet masa.

python
try:
    async with asyncio.timeout(2.0):
        result = await aggregate(user_id)
except TimeoutError:
    return degraded_response("deadline")
except asyncio.CancelledError:
    raise

Pembatalan luaran mesti terus merambat ke atas, bukan menjadi respons yang berjaya. Rekodkan tamat masa dan pembatalan pengguna sebagai punca yang berbeza.

Langkah 5: Fahami perbezaan TaskGroup berbanding gather

asyncio.gather() biasanya menyebarkan pengecualian pertama kepada penunggunya, tetapi tugas adik-beradik tidak semestinya dibatalkan; pengembalian secara serta-merta boleh menjadikannya yatim. TaskGroup mengikat jangka hayat tugas pada skop dan membatalkan adik-beradik apabila berlaku kegagalan.

gather(return_exceptions=True) berguna untuk kontrak kegagalan separa yang eksplisit, tetapi setiap hasil mesti diperiksa. Ia bukan pengganti untuk pembersihan berstruktur, dan kumpulan tidak boleh mencipta tugas latar belakang tanpa had.

Langkah 6: Uji susunan kegagalan dan pembersihan

Suntik kegagalan pengguna dahulu, kegagalan pengesyoran dahulu, kegagalan serentak, pembatalan pemanggil, jumlah tamat masa keseluruhan, tamat masa pemacu, dan finally yang gagal. Sahkan bahawa adik-beradik menerima pembatalan, sumber dikembalikan, tiada tugas yang kekal tergantung, dan setiap pengecualian boleh dikaitkan dengan anak.

Log nama tugas, ID permintaan, tempoh, punca pembatalan, jenis pengecualian, dan panggilan hiliran tanpa muatan persendirian. Ulang ujian I/O perlahan untuk mendedahkan keadaan berlumba (race conditions); ujian yang kesemuanya berjaya adalah tidak mencukupi.

Jawapan model

“Saya mencipta tiga tugas di dalam TaskGroup, menjadikan pengguna dan pesanan sebagai mandatori, dan membenarkan pengesyoran diturunkan mutunya. Skop menggabungkan tugas; kegagalan bukan pembatalan membatalkan adik-beradik dan membangkitkan ExceptionGroup, yang dikelaskan oleh except*. Setiap tugas I/O menghantar tamat masa dan melepaskan sambungan dalam finally.”

“Tamat masa luar membekalkan satu bajet masa yang dikongsi dan CancelledError disebarkan. Kerja pasca-respons dihantar ke baris gilir tahan lasak. Saya menyuntik kegagalan serentak, pembatalan, tamat masa, dan I/O perlahan serta mengesahkan tiada tugas yatim atau kebocoran sumber.”

Kesilapan biasa

  • Kembali selepas create_task kosong → tugas menjadi yatim → kekalkan tugas dalam skop TaskGroup.
  • Menelan CancelledError induk tidak dapat berhenti → bersihkan dan bangkitkan semula (re-raise).
  • Memberikan tamat masa penuh kepada setiap tugas → jumlah kependaman (latency) melambung → kongsi satu bajet masa.
  • Memperlakukan ExceptionGroup sebagai satu ralat → bukti selari hilang → kelaskan dengan except*.
  • Hanya membatalkan tugas Python → kerja pangkalan data atau HTTP diteruskan → gunakan pembatalan pemacu atau tamat masa.
  • Menggunakan gather untuk setiap kes → kegagalan separa dan pembersihan menjadi kabur → tentukan kontrak penurunan mutu.

Soalan susulan dan respons

Soalan susulan 1: Adakah TaskGroup menghentikan I/O asas secara serta-merta?

Tidak. Pemacu mesti menyokong pembatalan atau tamat masa pernyataan; jika tidak, asingkan sambungan atau alihkan kerja ke tugas latar belakang yang terikat.

Soalan susulan 2: Mengapa tidak membatalkan kumpulan apabila pengesyoran gagal?

Gunakan kontrak perniagaan. Pengesyoran pilihan boleh ditangkap dan digantikan dengan hasil kosong; kerja kritikal keselamatan atau pengebilan harus membiarkan pengecualian terlepas dan membatalkan adik-beradik.

Soalan susulan 3: Bagaimana anda memetakan ExceptionGroup kepada respons API?

Petakan ralat kebergantungan yang diketahui kepada 503 yang selamat atau hasil yang diturunkan mutunya dan rekodkan ralat anak. Ralat yang tidak diketahui diserahkan kepada pengendali global; jangan sesekali mengembalikan surihan timbunan (stack trace) dalaman kepada klien.

Soalan susulan 4: Kerja mana yang tergolong di luar TaskGroup?

E-mel, pengindeksan, dan penulisan kelompok yang mesti diteruskan selepas respons harus disimpan ke dalam baris gilir dan dicuba semula oleh pekerja (worker). Skop permintaan hanya boleh mengandungi kerja sepanjang hayat permintaan itu sahaja.

Soalan susulan 5: Bagaimana jika finally membangkitkan pengecualian semasa pembatalan?

Pastikan pembersihan adalah kecil dan boleh diperhatikan, sertakan kegagalan pembersihan sebagai konteks, dan kekalkan pembatalan asal atau pengecualian perniagaan supaya punca utama tidak digantikan.

Sumber awam

Soalan berkaitan

Alat temu duga berkaitan

Gunakan Tangkapan Skrin untuk gesaan pengekodan

Tangkap soalan, kemudian selesaikan kekangan, penyelesaian, kod, kes pinggir dan kerumitan mengikut urutan.

Lihat alat