协程函数未await会导致异常静默丢失,因协程对象未被调度或回收时异常被丢弃;add_done_callback对原始协程无效,仅适用于Task;应启用asyncio.debug、全局异常钩子及类型检查从源头防范。

协程函数没await,异常直接消失
Python协程里调用 async def 函数但忘了 await,它不会报错,只会返回一个 coroutine 对象——而这个对象如果没人消费,里面抛的异常就彻底丢弃。你看到程序“静默失败”,日志里连 traceback 都没有。
- 典型场景:在同步代码里直接调用异步函数(比如
fetch_data()而不是await fetch_data()),或漏掉循环里的await - 异常吞噬发生在事件循环退出前未调度该协程,或协程被 gc 回收时触发
__del__打印警告(但仅限 debug 模式) - CPython 3.12+ 默认开启
asyncio.debug=True时,会打印RuntimeWarning: coroutine 'xxx' was never awaited;旧版本默认不提示 - 示例:
async def risky():<br> raise ValueError("boom")<br><br># ❌ 静默失效<br>risky() # 返回 coroutine 对象,异常不触发
add_done_callback 为什么抓不到异常
add_done_callback 是给 Task 或 Future 绑定回调,但它只在任务“完成”后触发——而没 await 的协程根本不会变成 Task,更不会完成。所以你对原始协程对象调用 add_done_callback 是无效的,方法不存在,会报 AttributeError。
- 只有显式用
asyncio.create_task()或loop.create_task()包装,才得到可绑定回调的Task实例 - 直接对协程对象调
add_done_callback:报错AttributeError: 'coroutine' object has no attribute 'add_done_callback' - 即使你误写成
asyncio.create_task(risky()).add_done_callback(...),也只对那个 Task 生效;但原始漏 await 的调用点依然存在,异常照吞
怎么快速定位漏 await 的协程调用
靠肉眼扫代码不可靠,尤其在大型项目或动态调用路径里。真正有效的办法是启用 asyncio 的异常钩子 + 启动时强制 debug 模式。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 启动时加环境变量:
PYTHONASYNCIODEBUG=1,或代码开头设asyncio.get_event_loop().set_debug(True) - 安装全局异常钩子捕获未处理的协程异常:
<pre class="brush:php;toolbar:false;">def handle_coro_exception(loop, context):<br> if "exception" in context:<br> print("Uncaught in task:", context.get("exception"))<br>loop.set_exception_handler(handle_coro_exception) - 用
pytest-asyncio 运行测试时,加上 <code>--asyncio-mode=auto和-W default::RuntimeWarning可暴露所有未 await 警告 - 注意:某些 IDE(如 PyCharm)的调试器会自动 await 协程用于变量求值,导致你在断点里看不到异常——换终端运行验证
正确做法:从源头堵住漏 await
别依赖事后补救。协程调用必须明确区分“发起”和“等待”,漏掉任一环节都可能让异常沉底。
立即学习“Python免费学习笔记(深入)”;
- 所有
async def函数调用处,检查是否在async函数内且用了await;若在同步函数里,必须用asyncio.run()或loop.run_until_complete()包一层 - 避免在构造函数、
__init__、信号处理、atexit 等同步上下文中直接调协程——这类地方几乎不可能合法 await - 用类型检查工具辅助:mypy +
mypy-extensions可识别Coroutine类型未被 await;pyright 也能标出 “Expected awaitable” 提示 - CI 中加检查:用
grep -r "def.*async" . | grep -v "await"做粗筛(不完美但有用)
最麻烦的不是找不到异常,而是异常发生在某个被忽略的后台任务里,比如 asyncio.create_task(log_writer()) 内部出错却没加 error handler——这种 Task 一旦异常,除非显式 task.exception() 检查,否则永远沉底。

















