8.6. ข้อยกเว้น#

ข้อยกเว้นภายในสคริปต์ asyncio ทำงานเกือบเหมือนกับใน Python ทั่วไป มันแพร่กระจายขึ้นไปตามสายการเรียกจนกว่าบางสิ่งจะจับมัน เกือบ เพราะงานทำงานพร้อมกัน ดังนั้นเส้นทาง "ขึ้น" ไม่ใช่เส้นทางที่สร้างงาน หน้านี้ครอบคลุมว่าข้อยกเว้นไปที่ไหนในแต่ละรูปแบบทั่วไป

8.6.1. ภายในคอรูทีนเดียว#

try/except ภายในคอรูทีนจับข้อยกเว้นที่ถูกยกขึ้นโดยสิ่งที่มัน awaitในแบบปกติ:

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

ไม่มีอะไรเฉพาะ asyncio ที่นี่ ส่วน except เห็นข้อยกเว้นที่ถูกยกขึ้นภายใน fetch เหมือนกับที่มันเป็นการเรียกฟังก์ชันปกติ

8.6.2. ในงานที่แอปพลิเคชันกำลัง await#

เมื่อคอรูทีนที่ทำงานเป็น Task ยกข้อยกเว้น ข้อยกเว้นนั้นจะถูกเก็บไว้ในงาน ครั้งถัดไปที่บางสิ่ง awaitงานนั้น ข้อยกเว้นจะถูกยกขึ้นใหม่ที่ await

task = asyncio.create_task(may_fail())
try:
    result = await task
except OSError:
    log("may_fail failed")

เช่นเดียวกันนี้ใช้กับ asyncio.gather() พฤติกรรมเริ่มต้น ซึ่งก็คือ ลูกตัวหนึ่งยก ตัวอื่นถูกยกเลิก ข้อยกเว้นแพร่กระจายออกจาก gather มาจากกลไกนี้

8.6.3. ในงานที่ไม่มีใคร await#

งานที่ไม่มีใคร awaitเลยคือกรณีที่ต้องใส่ใจ ข้อยกเว้นยังคงเกิดขึ้น loop สังเกตว่างานเสร็จสิ้นพร้อมข้อยกเว้นที่ไม่ได้รับการจัดการ แต่ไม่มี await ให้มันปรากฏ พฤติกรรมเริ่มต้นคือพิมพ์ traceback ผ่าน sys.stderr และทำงานต่อไป ซึ่งเหมาะสำหรับการวินิจฉัยที่ไม่มีคนดูแล แต่ไม่เหมาะกับแอปพลิเคชันที่ต้องการทราบ

การแก้ไขที่ถูกต้องมักคือการ await งาน นั้น ไม่ว่าจะโดยตรงโดยจำตัวจัดการและ await ระหว่างการปิด หรือโดยปริยายผ่าน gather() หรือ wait_for() รูปแบบ "การปิดแอปพลิเคชัน" ของหน้า การหมดเวลาและการยกเลิก จัดการกรณีนี้สำหรับงานพื้นหลังที่ยาวนานที่สคริปต์ทั่วไปสร้างขึ้น

8.6.4. ตัวจัดการข้อยกเว้นแบบกำหนดเอง#

เมื่อการพิมพ์ traceback และดำเนินการต่อไปอย่างเงียบๆ ไม่เพียงพอ loop จะเปิดเผย hook ซึ่งก็คือ Loop.set_exception_handler ที่แอปพลิเคชันสามารถแทนที่เพื่อทำอะไรบางอย่างแทน:

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)

อาร์กิวเมนต์ context คือ dict ที่มีคีย์ 'message', 'exception' และ 'future' ข้อยกเว้นอาจขาดหายไปในเหตุการณ์แบบคำเตือนบางอย่าง นั่นเป็นเหตุผลที่ตัวอย่างใช้ .get()

การใช้งานทั่วไปคือการบันทึกความล้มเหลวไปยังแฟลช กะพริบ LED ข้อผิดพลาด หรือส่งต่อไปยังการรีบูต watchdog หน้า loop control ครอบคลุมพื้นผิวทั้งหมดของ loop hooks

8.6.5. KeyboardInterrupt#

เมื่อสคริปต์ถูกหยุดจากภายนอก ซึ่งโดยปกติคือ IDE ที่ขอให้หยุด คำขอจะมาถึงภายในสคริปต์เป็น KeyboardInterrupt ภายใน asyncio.run() มันแพร่กระจายในแบบเดียวกับข้อยกเว้นที่ไม่ได้รับการจัดการอื่นๆ: main ถูกยกเลิก งานทุกชิ้นที่ loop ติดตามอยู่ก็ถูกยกเลิกด้วย และ KeyboardInterrupt ถูกยกขึ้นใหม่ออกจาก asyncio.run() ส่วน finally ทำงานระหว่างออก ดังนั้นรูปแบบการทำความสะอาดเดียวกับหน้าการยกเลิกคือสิ่งที่จัดการมัน