Python 异常在协程中的处理机制

Python协程异常不立即抛出,而是挂起至await时才浮现;未await的协程异常被静默丢弃;await链中异常逐层传递;取消操作引发CancelledError,需显式处理。

Python 协程中异常的传播和处理,与普通函数不同:它不是立即抛出,而是被“挂起”并延迟到 await 表达式求值时才真正浮现。理解这一机制,是写出健壮异步代码的关键。

协程异常不会自动向上冒泡

一个协程内部发生异常(如 ValueError),并不会像同步函数那样立刻中断调用栈。它会被封装进协程对象的状态中,直到该协程被 await —— 此时异常才被重新抛出到 awaiter 所在的上下文中。

例如:

async def bad_coro():
    raise RuntimeError("oops")

async def main(): coro = bad_coro() # ✅ 不会报错,只是创建协程对象 await coro # ❌ 这里才真正抛出 RuntimeError

未 await 的协程异常会被静默丢弃

如果协程对象被创建后从未被 await,其内部异常将永远不会触发,也不会警告。这容易导致逻辑错误被掩盖。

  • 避免直接丢弃协程对象,比如写成 bad_coro() 而不 await 或赋值给变量
  • 使用 asyncio.create_task() 启动协程时,异常仍会传播到 task 对象;但若 task 未被 awaittask.exception() 检查,也会被忽略
  • 启用 asyncio.get_event_loop().set_exception_handler() 可捕获未处理的 task 异常,用于调试

异常可在 await 链中逐层传递

多个 await 嵌套时,异常会沿调用链向上传播,行为类似同步函数的异常冒泡:

async def inner():
    raise KeyError("missing")

async def middle(): await inner() # 异常在此处捕获并继续上抛

async def outer(): try: await middle() except KeyError as e: print("caught:", e) # ✅ 这里能捕获

注意:不能在非 await 上下文中用 try/except 捕获协程内部异常——必须 await 后才能进入异常处理逻辑。

取消操作会引发 CancelledError

调用 task.cancel() 或超时触发取消时,协程会在下一个

await 点收到 asyncio.CancelledError(继承自 BaseException)。它不会被普通 except Exception: 捕获,需显式处理或使用 except BaseException:(不推荐)。

  • 推荐在关键清理逻辑中使用 try/finallyasync with 确保执行
  • 可在协程内用 if asyncio.current_task().cancelled(): 主动检查是否被取消
  • 调用 await asyncio.shield(some_coro) 可防止内部协程被外部取消影响