8.6. Eksepsi¶
Eksepsi di dalam skrip asyncio berperilaku hampir sama seperti di Python biasa -- eksepsi merambat ke atas rantai pemanggilan hingga sesuatu menangkapnya. Hampir karena task berjalan secara paralel, sehingga jalur "ke atas" bukan jalur yang membuat task tersebut. Halaman ini membahas ke mana eksepsi pergi dalam setiap bentuk yang umum.
8.6.1. Di dalam satu coroutine¶
Sebuah blok try/except di dalam coroutine menangkap eksepsi yang di-raise oleh apa pun yang di-awaits-nya, dengan cara biasa:
async def fetch_with_retry(url):
for attempt in range(3):
try:
return await fetch(url)
except OSError as e:
last_error = e
raise last_error
Tidak ada yang spesifik asyncio di sini -- klausa except melihat eksepsi yang di-raise di dalam fetch seolah-olah itu adalah pemanggilan fungsi biasa.
8.6.2. Dalam task yang sedang di-await aplikasi¶
Ketika coroutine yang berjalan sebagai Task memunculkan eksepsi, eksepsi tersebut disimpan pada task. Lain kali sesuatu awaits task tersebut, eksepsi di-raise kembali pada await
task = asyncio.create_task(may_fail())
try:
result = await task
except OSError:
log("may_fail failed")
Hal yang sama berlaku untuk asyncio.gather(). Perilaku default -- satu anak memunculkan eksepsi, yang lain dibatalkan, eksepsi merambat keluar dari gather -- berasal dari mekanisme ini.
8.6.3. Dalam task yang tidak di-await siapapun¶
Task yang tidak pernah di-awaits oleh siapa pun adalah kasus yang perlu diperhatikan. Eksepsi tetap terjadi; loop menyadari task selesai dengan eksepsi yang tidak tertangani; namun tidak ada await untuk memunculkannya. Perilaku default adalah mencetak traceback melalui sys.stderr dan terus berjalan -- yang baik untuk diagnostik tanpa pengawasan, namun kurang cocok untuk aplikasi yang ingin mengetahuinya.
Perbaikan yang tepat biasanya adalah await task tersebut. Baik secara langsung, dengan mengingat handle dan await-nya saat shutdown, atau secara implisit melalui gather() atau wait_for(). Pola "menutup aplikasi" di halaman Timeout dan pembatalan menangani kasus ini untuk task latar belakang berumur panjang yang biasanya dimunculkan skrip.
8.6.4. Handler eksepsi kustom¶
Ketika traceback-dan-lanjutkan yang diam-diam tidak cukup, loop mengekspos sebuah hook -- Loop.set_exception_handler -- yang dapat di-override aplikasi untuk melakukan sesuatu yang lain:
def handler(loop, context):
print("asyncio:", context.get("message"))
if "exception" in context:
sys.print_exception(context["exception"])
loop = asyncio.get_event_loop()
loop.set_exception_handler(handler)
Argumen context adalah dict dengan kunci 'message', 'exception', dan 'future'. Eksepsi mungkin tidak ada pada event tertentu bergaya peringatan, itulah mengapa contoh ini menggunakan .get().
Penggunaan umum adalah mencatat kegagalan ke flash, mengedipkan LED kesalahan, atau eskalasi ke reboot watchdog. Halaman kontrol loop membahas permukaan lengkap dari hook loop.
8.6.5. KeyboardInterrupt¶
Ketika sebuah skrip dihentikan dari luar -- biasanya oleh IDE yang memintanya berhenti -- permintaan tiba di dalam skrip sebagai KeyboardInterrupt. Di dalam asyncio.run() ia merambat seperti eksepsi yang tidak tertangani lainnya: main dibatalkan, setiap task yang dilacak loop juga dibatalkan, dan KeyboardInterrupt di-raise kembali keluar dari asyncio.run(). Klausa finally berjalan saat keluar, sehingga pola pembersihan yang sama dari halaman pembatalan adalah yang menanganinya.