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

ContextVar内存泄漏:已完成异步任务为何仍持有MyObj引用?

异步任务中ContextVar引发的内存泄漏疑惑

我遇到一个内存泄漏问题:明明run(1)和run(2)任务已经执行完成,上下文也应该被清理了,但它们却还持有MyObj的引用。手动调用gc.collect()后,这两个已完成的任务依然存在。相关代码、运行结果及引用关系如下:

测试代码

import asyncio
import gc
from contextvars import ContextVar

import objgraph

ctx = ContextVar('ctx')

class MyObj:
    def __init__(self, value):
        self.value = value


async def run(value):
    ctx.set(MyObj(value))

async def main():
    await asyncio.gather(run(1), run(2))
    gc.collect()
    print('# of MyObj=', objgraph.count('MyObj'))
    for obj in objgraph.by_type('MyObj'):
        print('MyObj.value-',obj.value)
    print('outer ctx=', ctx.get(None))
    objgraph.show_backrefs(objgraph.by_type('MyObj'), filename='ctx.png', max_depth=5)

asyncio.run(main())

运行输出

# of MyObj= 2
MyObj.value= 2
MyObj.value= 1
outer ctx= None

引用关系图

objgraph引用关系

原因解析

这是因为asyncio会保留已完成任务的上下文快照,ContextVar的绑定和任务的上下文强关联,不会自动被清理:

  • 每个异步任务执行时,都会生成独立的上下文副本,ctx.set(MyObj(value))是把对象绑定到当前任务的专属上下文里;
  • 任务完成后,asyncio不会立刻销毁任务对象及其关联上下文——这些任务会被暂存到事件循环的已完成任务列表中(不同Python版本存储位置可能有差异,但核心是asyncio会缓存已完成任务);
  • 手动调用gc.collect()时,只要任务对象还被事件循环持有,它们关联的ContextVar绑定(也就是你的MyObj实例)就无法被垃圾回收。

你看到outer ctx= None只是当前主线程的ContextVar没有绑定值,不代表任务内部的上下文已经被清理。

解决办法

  • 在任务结束前主动清除绑定:修改run函数,在最后调用ctx.set(None);
  • 等事件循环结束后再执行GC:asyncio.run()会自动关闭事件循环,此时已完成任务的引用会被释放,再调用gc.collect()就能回收MyObj实例。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 12:03:11