8.6. Ngoại lệ

Ngoại lệ bên trong một tập lệnh asyncio hoạt động gần giống như trong Python thông thường -- chúng lan truyền lên chuỗi gọi cho đến khi có gì đó bắt chúng. Gần giống vì các task đang chạy song song, vì vậy đường đi "lên" không phải là đường đã tạo ra task. Trang này đề cập đến nơi ngoại lệ đi trong mỗi hình dạng phổ biến.

8.6.1. Bên trong một coroutine

Một khối try/except bên trong coroutine bắt ngoại lệ được phát ra bởi bất cứ thứ gì nó awaits, theo cách thông thường:

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

Không có gì đặc thù asyncio ở đây -- mệnh đề except thấy ngoại lệ được phát ra bên trong fetch như thể nó là một lệnh gọi hàm thông thường.

8.6.2. Trong một task mà ứng dụng đang await

Khi một coroutine đang chạy như một Task phát ra ngoại lệ, ngoại lệ được lưu trữ trên task. Lần tiếp theo khi có gì đó awaits task đó, ngoại lệ được phát lại tại await

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

Điều tương tự áp dụng cho asyncio.gather(). Hành vi mặc định -- một coroutine con phát ra ngoại lệ, các coroutine khác bị hủy, ngoại lệ lan truyền ra khỏi gather -- xuất phát từ cơ chế này.

8.6.3. Trong một task không có ai await

Một task mà không ai awaits là trường hợp cần chú ý. Ngoại lệ vẫn xảy ra; vòng lặp nhận thấy task đã hoàn thành với một ngoại lệ chưa được xử lý; nhưng không có await nào để nó xuất hiện. Hành vi mặc định là in traceback qua sys.stderr và tiếp tục chạy -- điều đó tốt cho chẩn đoán không có người giám sát, nhưng không phù hợp với ứng dụng muốn biết.

Cách sửa đúng thường là await task. Hoặc trực tiếp, bằng cách nhớ tay cầm và await nó trong quá trình tắt máy, hoặc ngầm định thông qua gather() hoặc wait_for(). Mẫu "tắt ứng dụng" của trang Hết thời gian chờ và hủy tác vụ bắt trường hợp này cho các task nền tồn tại lâu dài mà một tập lệnh điển hình tạo ra.

8.6.4. Trình xử lý ngoại lệ tùy chỉnh

Khi in traceback và tiếp tục không đủ, vòng lặp cung cấp một móc nối -- Loop.set_exception_handler -- mà ứng dụng có thể ghi đè để làm điều gì đó khác:

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)

Đối số context là một dict với các khóa 'message', 'exception', và 'future'. Ngoại lệ có thể bị thiếu trong một số sự kiện kiểu cảnh báo, đó là lý do tại sao ví dụ sử dụng .get().

Các cách dùng điển hình là ghi nhật ký lỗi vào flash, nhấp nháy LED báo lỗi, hoặc leo thang lên watchdog khởi động lại. Trang kiểm soát vòng lặp đề cập đến toàn bộ bề mặt của các móc nối vòng lặp.

8.6.5. KeyboardInterrupt

Khi một tập lệnh bị dừng từ bên ngoài -- thường là bởi IDE yêu cầu nó dừng lại -- yêu cầu đến bên trong tập lệnh dưới dạng KeyboardInterrupt. Bên trong asyncio.run() nó lan truyền theo cách giống như bất kỳ ngoại lệ chưa được xử lý nào khác: main bị hủy, mọi task mà vòng lặp đang theo dõi cũng bị hủy, và KeyboardInterrupt được phát lại ra khỏi asyncio.run(). Các mệnh đề finally chạy trong quá trình thoát, vì vậy cùng một mẫu dọn dẹp từ trang hủy là thứ xử lý nó.