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
引用关系图

原因解析
这是因为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
相关产品推荐
相关产品推荐

