You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何asyncio.wait超时后仍保留任务引用引发内存泄漏?

关于asyncio.wait超时导致内存泄漏的问题解惑

问题复现

近期发现并复现了asyncio.wait引发的内存泄漏问题:程序通过循环定期执行逻辑,直到stop_event被触发,简化代码如下:

async def main():
  stop_event = asyncio.Event()

  while True:
    # 执行业务逻辑
    await asyncio.wait([stop_event.wait()], timeout=0.0001)

asyncio.run(main())

运行后内存占用数分钟内飙升至数百MB,通过gc.get_referrers排查发现:每次调用asyncio.wait都会创建持有stop_event.wait()返回对象引用的任务,且这些任务会被永久保留,len(asyncio.all_tasks())持续增长;只有调用stop_event.set()时,所有任务才会结束,内存占用骤降。

解决方案

查阅官方文档得知:

Unlike wait_for(), wait() does not cancel the futures when a timeout occurs.

改用asyncio.wait_for后,内存泄漏问题消失,代码如下:

async def main():
  stop_event = asyncio.Event()

  while True:
    # 执行业务逻辑
    try:
      await asyncio.wait_for(stop_event.wait(), timeout=0.0001)
    except asyncio.TimeoutError:
      pass

asyncio.run(main())

核心疑问解惑

1. 为何超时后要保留任务引用?

asyncio.wait的设计定位是将未完成的future控制权交还给调用者:它会返回(done, pending)两个集合,分别包含已完成和未完成的future。为了让你能对pending集合中的future进行后续操作(比如等待完成、手动取消),asyncio必须保留这些任务的引用——如果不保留,这些pending的任务可能会被垃圾回收,导致你无法再操作它们。因此超时后,那些等待stop_event的任务会被留在asyncio的活跃任务列表中,直到它们自然完成(stop_event.set())或被手动取消。

2. 若不取消future但不保留任务引用是否可行?

不可行。原因有两点:

  • 若asyncio不保留任务引用,Python的垃圾回收机制可能会回收处于等待状态的任务,导致任务被意外终止,引发不可预测的行为;
  • asyncio的任务调度器依赖内部任务列表跟踪所有活跃任务,若任务不在列表中,调度器无法对其进行管理,违背了异步任务的调度逻辑。

因此,只要任务处于未完成的活跃状态(包括等待事件),asyncio就必须保留其引用,直到任务完成或被取消。

内容的提问来源于stack exchange,提问作者s1m0n

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 14:01:02