症状
警告在最亡羊补牢的时刻——垃圾回收时——才到来:RuntimeWarning: coroutine ‘refresh_cache’ was never awaited,附带 created at 回溯,精确点名构造协程又丢弃它的那一行。业务症状与之配对出现:refresh_cache 本该完成的副作用(缓存刷新、审计写入、通知发送)从未发生。函数从同步改为异步后某个调用点没同步更新、条件分支返回协程对象而不是 await 它,都是同一错误的变体。
诊断思路
先在 CI 里把警告升级为错误(python -W error::RuntimeWarning),在调用时刻而不是嘈杂的停机时刻定位;再用 grep 找出所有不带 await、不带 create_task、不带 ensure_future 的 async 函数调用行——每一行都是孤儿工厂;最后用 python -X dev 跑应用,开发模式会为每个未等待协程打印带源码行的创建回溯,把停机噪音变成可执行的清单。
根因
调用 async 函数并不会执行它,只是构造一个协程对象。没有 await、create_task 或 gather,函数体永远不运行,解释器在垃圾回收销毁这个未等待协程时发出 RuntimeWarning——副作用缺失的原因就在于此。
修复与验证
结果重要就 await;确属 fire-and-forget 就用 asyncio.create_task 调度并保存引用(放进集合、完成时移除——不存引用的任务可能在飞行中被垃圾回收悄悄取消);函数体内没有任何 await 的,改回同步函数反而正确。验证时以 RuntimeWarning 即错误的模式跑受影响流程,断言零异常,并实际观察目标副作用(缓存条目、数据行、通知)可被观测。CI 常态保留 -W error::RuntimeWarning,让未来的孤儿在构建期而不是停机期暴露。
代码示例
RuntimeWarning: coroutine 'refresh_cache' was never awaited
Coroutine created at (most recent call last)
File "app/api.py", line 42, in handler
refresh_cache(user_id) <- called, never run
python -W error::RuntimeWarning -m pytest
async function refresh() {
const data = await load();
cache.set(key, data);
}
await refresh();
const pending = new Set();
function fireAndForget(promise) {
pending.add(promise);
promise.finally(() => pending.delete(promise));
}
fireAndForget(refresh()); // runs to completion, tracked