事件循环不直接捕获异常,而是通过await、task和异常处理器协同处理:await使异常向上冒泡可被try-except捕获;未await的协程不会触发异常;task需显式await、调用exception()或add_done_callback()处理;set_exception_handler为全局兜底;gather/wait需注意异常传播策略。

事件循环本身不直接“捕获”异常,而是负责调度协程并传递异常。真正起作用的是你如何组织 await、任务(Task)和异常处理器——关键不在循环里“抓”,而在执行路径上“接住”。
await 是最直接的异常入口
只要你在协程中用 await 等待另一个协程,它的异常就会原样抛出,被外层 try-except 捕获。
- ✅ 正确:await 一个协程 → 异常向上冒泡 → 可被当前函数的 try-except 拦住
- ❌ 错误:只调用 async 函数却不 await(比如
faulty_coroutine()),它返回一个未等待的 coroutine 对象,异常根本不会触发,也不会被捕获 - ⚠️ 注意:await 的必须是可等待对象(协程、Task、Future)。如果 await 了一个已创建但未启动的 Task,仍需确保它最终被执行
Task 需要显式“取异常”或“等结果”
用 asyncio.create_task() 启动的任务脱离当前上下文,外部 try-except 无法覆盖它。必须主动处理:
- await task:最简单,异常会重新抛出,适合串行依赖场景
- task.exception():非阻塞检查。任务已完成且失败时返回异常对象;未完成或成功则返回 None
- add_done_callback():注册回调,在任务结束时检查 result() 或 exception(),适合后台任务监控
全局兜底:loop.set_exception_handler
这是最后一道防线,捕获所有“漏网”的未处理异常(比如没 await 的 Task、未 catch 的回调错误)。
- 它不替代业务层 try-except,而是防止静默崩溃和日志丢失
- context 参数包含 'exception'(异常对象)、'message'(描述)、'future'(出问题的 Task/Future)
- 适合记录报警、清理资源,但不能恢复执行流
批量任务要防“中断+遗漏”
像 asyncio.gather() 和 asyncio.wait() 这类组合操作,默认行为容易掩盖问题:
-
gather(..., return_exceptions=False)(默认):任一失败就抛出第一个异常,其余任务可能被取消或继续运行,状态难追踪 -
gather(..., return_exceptions=True):所有结果(含 Exception 实例)统一返回,需遍历判断isinstance(x, Exception) -
wait()不抛异常,只分组返回 done/pending,必须手动遍历 done 中每个 task 调用task.exception()











