async/await异常捕获需分层处理:await处用try-except捕获协程内异常;create_task启动的任务需通过task.exception()检查;gather需设return_exceptions=True;set_exception_handler仅作日志兜底。
它没失效,是你没在正确位置捕获。 async/await 语法里的 try-except 只能捕获当前协程内 await 立即抛出的异常;而通过 asyncio.create_task() 启动的任务,其异常会静默存入 Task 对象,不会自动冒泡到外层作用域。
await 表达式里必须显式包 try-except
这是最直接、最可控的捕获点。如果你 await 一个可能失败的协程(比如网络请求、文件读写),异常会在 await 那一刻抛出,try-except 就能拦住。
- ✅ 正确:在
await前后加try-except,尤其对单个关键操作 - ❌ 错误:只在外层函数开头写一个
try,却让多个await在里面“裸奔” - ⚠️ 注意:
await asyncio.sleep(0)或空await不会触发异常,但真实 I/O 操作几乎都可能抛出TimeoutError、ConnectionError等
用 asyncio.gather() 时别忽略 return_exceptions 参数
asyncio.gather() 默认行为是“任一任务失败,整个调用立即抛异常”,这常让人误以为异常被“吞了”——其实是它太急着失败了。设 return_exceptions=True 才能让所有结果(含异常对象)一起返回,便于统一检查。
- ✅ 推荐:并发发 5 个 HTTP 请求,加
return_exceptions=True,然后遍历结果,对每个result判断isinstance(result, Exception) - ❌ 危险:不设该参数,又没包
try-except,整个gather()调用一崩,你连哪个请求挂了都不知道 - ⚠️ 性能影响:设为
True不影响并发度,只是把异常转成普通返回值,后续处理成本略高一点
检查 done 任务集合时务必调用 task.exception()
当你用 asyncio.wait() 或手动轮询 asyncio.all_tasks() 时,拿到的 done 集合里可能混着成功和失败的任务。失败任务的异常不会自动抛出,必须主动查。
Python 3.14.3
微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。
- ✅ 必做:对每个
task在done中,先调task.exception()—— 返回None表示成功,否则是异常对象 - ❌ 常见错:只调
task.result(),它会重新抛异常,导致程序中断,失去批量处理机会 - ⚠️ 容易漏:如果任务还没完成(在
pending里),它的异常根本还没发生或尚未被捕获,不能提前查exception()
给事件循环配 set_exception_handler 是兜底不是替代
loop.set_exception_handler() 是最后一道防线,适合记录日志、告警,但它无法恢复执行流,也不能帮你定位是哪个 task、哪行代码出了问题。
- ✅ 应该:在主入口处注册 handler,打印
task的get_coro().__name__和 traceback - ❌ 不能依赖:把它当主力异常处理机制,结果线上报错只有“Unhandled exception in Task”,没上下文、没参数、没堆栈源码行号
- ⚠️ 关键限制:handler 里不能再
await,否则会卡死事件循环;只能做同步操作,如写文件、发 HTTP 日志
真正难的不是写 try-except,而是判断异常发生在哪一层:是 await 当前协程?是子 task 内部?还是 gather 汇总阶段?每层对应的捕获方式和检查手段完全不同。漏掉任意一层,异常就变成“静默失败”或“突然崩溃”。