Python协程异常沿await链向上冒泡,由try/except捕获或交由事件循环处理;Task封装异常,需await或exception()显式获取;gather默认快速失败,wait需手动检查;顶层未捕获异常会终止run但后台Task异常仅日志记录。

Python 协程中异常的传播机制与普通函数调用相似,但受 await 和协程调度逻辑影响,关键在于“谁在等待、谁抛出、谁捕获”。异常会沿 await 链向上冒泡,直到被 try/except 捕获,或到达事件循环顶层后终止任务。
await 表达式是异常传播的“桥梁”
当一个协程 A await 协程 B 时,B 中未捕获的异常不会静默吞掉,而是直接“抛回”给 A 的 await 语句处——就像函数调用中子函数抛异常会回到调用点一样。
- 如果 A 在该
await外围有try/except,就能捕获 B 抛出的异常; - 如果 A 没捕获,异常继续向上传播到 A 的调用方(比如另一个
await或asyncio.run()); - 若传播到最外层且无人处理,
asyncio会记录警告并标记对应Task为失败状态(task.exception()可查)。
Task 对象封装了异常的生命周期
通过 asyncio.create_task() 或 asyncio.ensure_future() 启动的协程,其异常不会直接冒泡到主线程,而是被绑定到该 Task 实例上。
- 任务运行中抛出未捕获异常 → 任务立即结束,状态变为
done(),exception()返回异常实例; - 主协程可通过
await task主动“取回”异常(此时异常再次抛出); - 也可用
task.exception()静默检查,避免触发传播; - 注意:若从不 await 也不检查 task,异常会被静默丢弃(仅记录日志),这是常见陷阱。
asyncio.gather() 等组合器的行为差异
多个协程并发执行时,异常传播取决于所用组合方式:
立即学习“Python免费学习笔记(深入)”;
-
await asyncio.gather(coro1(), coro2()):默认“快速失败”,任一协程异常 → 整个gather立即抛出该异常,其余协程可能被取消; - 加参数
return_exceptions=True:异常不传播,而是作为结果列表中的对应元素(类型为Exception子类); -
asyncio.wait()或asyncio.as_completed():需显式遍历完成的Task并调用result()或exception()才能感知异常。
事件循环顶层的兜底处理
最终未被捕获的协程异常会由事件循环接管:
-
asyncio.run(main())中,若main协程抛异常且未处理,循环会停止,并把异常原样抛出到同步上下文(你能看到 traceback); - 手动调用
loop.run_until_complete()时行为相同; - 但如果是后台
Task(如loop.create_task(...))未被 await,异常只打印到日志,主流程不受影响——这容易掩盖问题。










