await task 是捕获异常的硬性前提,因为 asyncio 的 Task 将异常封装在内部,只有 await 才会触发 result() 并重新抛出原始异常;不 await 则异常静默丢失,不检查 task.exception() 则错误埋入日志死角。

不 await 任务,异常就静默丢失;不检查 task.exception(),错误就埋进日志死角。
为什么 await task 是捕获异常的硬性前提
asyncio 的 Task 对象把异常“锁”在内部,直到你显式触发它。调用 await task 是唯一能激活异常传播链的操作——它等价于调用 task.result(),而后者会重新抛出原始异常。
常见错误是只创建任务、不 await,比如:
task = asyncio.create_task(some_coro())<br>await asyncio.sleep(1) # 异常此时已发生,但不会冒泡
这种写法下,即使 some_coro() 抛了 ValueError,也不会进入任何 except 块。
- 必须对每个要捕获异常的任务做
await task - 若并发多个任务,用
asyncio.gather(*tasks)更安全(默认return_exceptions=False,任一失败即中断) - 若想“失败不中断”,则显式传
return_exceptions=True,之后手动检查结果中是否为异常实例
如何安全检查未 await 的任务异常
有些任务你不能或不该立刻 await(比如后台心跳、日志上报),这时得靠 task.exception() 主动捞异常——它不抛错,只返回异常对象或 None。
注意:只有任务已结束(task.done() is True)时,task.exception() 才有意义;否则返回 None,不代表没异常,只是还没发生。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 先判断
task.done(),再调task.exception() - 返回值可能是
None(正常完成)、异常实例(失败)、或尚未结束(此时done()为False) - 不要用
task.result()替代——它会重抛异常,可能崩掉当前协程
asyncio.TaskGroup 中多异常怎么分类型处理
asyncio.TaskGroup 允许同时启动多个任务,并在退出时聚合所有异常。但它不按“第一个抛错就停”的方式工作,而是等全部任务结束(或被取消)后,把异常打包成 ExceptionGroup 或子类抛出。
关键点:except* ValueError 语法专门用于匹配 ExceptionGroup 中的某类异常子集,而 except ValueError 捕不到——因为外层不是单个 ValueError,而是一个容器。
- 用
except* ValueError捕获所有ValueError实例(哪怕有多个) - 用
except* Exception捕获其余所有异常,其.exceptions属性是元组,每个元素是原始异常 - 避免只写
except Exception,它会漏掉ExceptionGroup
全局兜底:set_exception_handler 能抓到什么
loop.set_exception_handler() 是最后一道防线,它捕获的是“从未被 retrieve 过”的 Task 异常——也就是那些既没 await、也没调过 task.exception() 或 task.result() 的任务所抛的错。
典型场景:后台任务被遗忘、回调里忘了 await、或者 asyncio.create_task() 后直接丢弃引用。
- 钩子函数中通过
context.get("exception")获取异常对象 - 它不阻止程序继续,只负责记录;无法恢复执行流
- 生产环境建议开启,配合
debug=True使用,能暴露协程创建位置和挂起点
最易被忽略的是:一个任务异常后,若没人去碰它,它就永远卡在 exception 状态里,既不报错也不释放——直到事件循环结束。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










