Pythonnet调用Python脚本后内存未释放及Shutdown行为异常求助
.NET 6 API中pythonnet内存泄漏与Shutdown()行为异常问题解答
现象原因分析
两种调用方式的内存差异核心在于Shutdown时机与活跃PyObject引用的冲突:
- 接口A内立即执行Shutdown:
pyFunc.InvokeMethod返回的result属于PyObject派生对象,Shutdown执行时该对象仍处于被引用状态(尚未返回给API调用方)。Python引擎关闭后,该对象对应的Python原生内存已被销毁,但托管层的对象资源因引擎已关闭无法正常清理,导致部分内存残留,无法回到初始状态。 - 接口B单独执行Shutdown:接口A返回
result后,该对象的生命周期已结束(API请求处理完成后被.NET GC回收),此时调用Shutdown时Python引擎无活跃的PyObject引用,可完整回收所有Python侧内存及托管侧关联资源,因此内存能回到200+M的初始水平。
Shutdown()是否是释放内存的唯一方式?
不是,它是最彻底的引擎级清理方式,但还有其他局部清理手段:
- 手动释放PyObject:所有通过pythonnet获取的PyObject(包括方法返回值、模块、对象实例)必须调用
Dispose(),或用using语句包裹,确保托管侧释放对Python对象的引用,让Python GC能回收对应内存。 - 触发Python GC:调用
PythonEngine.Collect()(可连续调用两次处理循环引用),配合.NET的GC.Collect(),能回收Python侧无引用的对象,但无法清理引擎级全局资源(如模块缓存、类型信息)。
但如果Python脚本本身存在内存泄漏(如全局变量持有大对象、未关闭资源),上述局部手段无法彻底解决,此时Shutdown()是唯一能清理这些残留内存的方式。
为何脚本执行后内存无法自动释放?
根源在于跨Runtime的GC隔离与资源持有:
- .NET GC与Python GC完全独立,无法互相感知对方的内存引用状态。如果.NET侧未主动释放PyObject引用,Python侧的对象会一直被持有,无法被Python GC回收。
- Python脚本的内存残留:脚本中全局变量、缓存对象、未关闭的IO资源(如大文件、数据库连接)会长期驻留在Python解释器内存中,不会随单次调用结束自动清理。
- pythonnet内部缓存:框架会缓存类型映射、模块引用等信息,这些缓存不会在每次调用后自动清空,持续占用内存。
遗漏的关键操作
要解决内存持续增长问题,需注意以下几点:
- 用using包裹所有PyObject:确保托管引用及时释放,避免持有Python对象内存:
Action A() { using var pyResult = pyFunc.InvokeMethod(""); // 将PyObject转换为.NET原生类型(如string、int[])后返回,不要直接返回PyObject var netResult = ConvertToDotNetType(pyResult); return netResult; }
- 避免返回PyObject到API外层:直接返回PyObject会延长其生命周期,导致Python内存无法及时回收,必须转换为.NET原生类型后再返回。
- 单次Initialize,多次调用后清理:应用启动时只调用一次
PythonEngine.Initialize(),每次脚本调用后执行以下操作清理内存:
// 调用脚本后 PythonEngine.Collect(); PythonEngine.Collect(); // 处理Python循环引用 GC.Collect(); GC.WaitForPendingFinalizers();
- 排查Python脚本内存泄漏:使用Python的
tracemalloc工具检测脚本,排查全局变量、未释放资源等导致的内存残留。 - 谨慎使用Shutdown():Shutdown()会彻底关闭Python引擎,之后需重新Initialize才能再次调用脚本,频繁的Initialize/Shutdown会导致内存碎片和资源泄漏,仅在长期不再使用Python引擎时调用。
内容的提问来源于stack exchange,提问作者walker's a
相关产品推荐
相关产品推荐

