Python异步编程中,异步任务泄漏指的是创建后未被正确取消、等待或回收的异步任务持续堆积,最终引发内存上涨、事件循环负载过高的问题。这类问题通常隐蔽性较强,需要结合现象和工具逐步定位。

识别异步任务泄漏的典型现象
首先可以通过程序运行时的外部表现初步判断是否出现任务泄漏:
- 程序内存占用随运行时间持续线性增长,且没有回落趋势
- 事件循环的待处理任务数量持续增加,CPU占用率异常升高
- 程序响应速度逐渐变慢,甚至出现无响应的情况
- 运行一段时间后抛出内存不足相关的错误
基础排查步骤
1. 统计活跃任务数量
可以通过asyncio.all_tasks()方法获取当前事件循环中的所有活跃任务,对比不同时间点的任务数量变化,判断是否存在任务持续堆积的情况。
import asyncio
import time
async def monitor_tasks(interval=5):
while True:
# 获取当前所有活跃任务
tasks = asyncio.all_tasks()
print(f"当前活跃任务数量: {len(tasks)}")
# 可选:打印每个任务的详细信息,方便定位泄漏任务
for task in tasks:
print(f"任务对象: {task}, 任务状态: {task.get_name()}")
await asyncio.sleep(interval)
async def main():
# 启动任务监控协程
asyncio.create_task(monitor_tasks())
# 这里放你的业务代码
await asyncio.sleep(3600)
if __name__ == "__main__":
asyncio.run(main())
2. 检查任务创建逻辑
重点排查是否存在只创建任务但从未等待或取消的情况,比如使用asyncio.create_task()后没有保存任务引用,也没有后续处理:
import asyncio
async def leak_task():
# 模拟耗时操作
await asyncio.sleep(1000)
async def wrong_logic():
# 错误示例:创建任务后没有保存引用,也没有等待,任务会成为孤立任务
asyncio.create_task(leak_task())
async def correct_logic():
# 正确示例:保存任务引用,后续可以等待或取消
task = asyncio.create_task(leak_task())
# 业务处理完成后取消任务
# task.cancel()
# 或者等待任务完成
# await task
if __name__ == "__main__":
asyncio.run(wrong_logic())
使用工具深入分析
1. 借助objgraph定位对象引用
如果任务泄漏和对象引用相关,可以使用objgraph工具查看异步任务对象的引用链,找到阻止任务被回收的引用来源。
import asyncio
import objgraph
async def test_task():
await asyncio.sleep(100)
async def main():
task = asyncio.create_task(test_task())
# 查看当前asyncio.Task对象的数量
print(objgraph.count("Task"))
# 查看Task对象的引用链
objgraph.show_backrefs(objgraph.by_type("Task")[0], max_depth=5)
if __name__ == "__main__":
asyncio.run(main())
2. 使用asyncio的调试模式
启动asyncio的调试模式后,会输出未被等待的任务相关警告,帮助快速发现未处理的任务。
import asyncio
import sys
async def unawaited_task():
await asyncio.sleep(10)
async def main():
# 创建任务后不等待
asyncio.create_task(unawaited_task())
await asyncio.sleep(1)
if __name__ == "__main__":
# 开启asyncio调试模式
asyncio.run(main(), debug=True)
常见泄漏场景及解决方式
| 泄漏场景 | 解决方式 |
|---|---|
| 创建任务后未保存引用,也未等待 | 保存任务引用,在合适的时机调用cancel()取消任务,或使用await等待任务完成 |
| 任务内部出现死循环或无限等待,无法结束 | 给任务添加超时控制,使用asyncio.wait_for设置任务最大执行时间 |
| 任务被全局变量或长生命周期对象引用,无法被回收 | 避免不必要的全局引用,任务完成后主动移除引用 |
| 异常导致任务未被正确处理,停留在 pending 状态 | 给任务添加异常处理逻辑,捕获任务执行中的异常,避免任务卡住 |
预防异步任务泄漏的建议
- 所有通过
asyncio.create_task()创建的任务都要有明确的生命周期管理方案 - 生产环境开启asyncio调试模式,及时发现未处理的任务
- 定期监控程序的活跃任务数量和内存占用情况,设置合理的告警阈值
- 避免在循环中无限制创建任务,必要时使用信号量控制并发任务数量