Pythonnet内存管理异常求助:CLR与Python GC执行顺序冲突
环境信息
- Pythonnet版本:3.0.2
- Python版本:3.9
- 操作系统:Windows 10
- .NET运行时:.NET Framework 4.8
问题背景
我尝试通过.NET Framework DLL在Python中打开大文件并访问其中的数据。
场景说明
- Pythonnet加载在执行操作的模块内。
- C#对象作为Python类的属性存在。
- Python类实现了
__del__()方法,需要在对象销毁前调用C#类中的方法。 - 我在C#析构函数中添加了日志打印,以了解CLR何时调用它。
异常情况
- 部分场景下,CLR先于Python的
__del__()调用析构函数,虽可通过异常处理解决,但实现不够优雅(见示例)。 - 另一些场景下,Python GC在Pythonnet.Runtime.Dll处理前释放内存,导致Python.Shutdown()中访问已释放内存区域,临时通过添加异常捕获和空指针检查解决,但实现非常糟糕。
如果熟悉该工具的资深人士能指出Python端代码的问题,我会立即修正;若确认是工具本身问题,我将提交PR分享修复方案。
更新:补充代码示例
Python类代码
import pythonnet from pythonnet import load load("netfx") import clr from time import time try: clr.AddReference('FileManager.Library') import FileManager.Library except System.IO.FileNotFoundException: print("[File loader] DLL not found!") class FileLoader: def __init__(self, filePath): self.dataLoader = FileManager.Library.FileReader(filePath) self.isClosed = False def load_data(self): return self.dataLoader.GetDataPoints() def close(self): self.dataLoader.Close() self.isClosed = True def __del__(self): print(f"[FileLoader - {time()}] Freeing memory!", flush=True) if not self.isClosed: self.close()
Python主程序
from FileManager.FileLoader import FileLoader if __name__ == "__main__": fl = FileLoader("data.csv") data = fl.load_data() print("Done!")
C#类库代码
using System; using System.Collections.Generic; using System.Text; using System.IO; namespace FileManager.Library { public class FileReader: IDisposable { private string filePath; private int objectId; private bool isDisposed = false; private List<DataPoint> fileContent; private DateTimeOffset dto = new DateTimeOffset(DateTime.UtcNow); ~FileReader() { if(!isDisposed) Dispose(); System.Console.WriteLine($"[C# - {dto.ToUnixTimeMilliseconds().ToString()} ] Deleting {objectId}"); } public FileReader(string filePath) { this.filePath = filePath; fileContent = new List<DataPoint>(); objectId = this.GetHashCode(); LoadFile(); } private void LoadFile() { if (!File.Exists(filePath)) { throw new FileNotFoundException($"Cannot open file{filePath}"); } const Int32 BufferSize = 128; using (var fileStream = File.OpenRead(filePath)) using (var streamReader = new StreamReader(fileStream, Encoding.UTF8, true, BufferSize)) { String line; while ((line = streamReader.ReadLine()) != null) { var data = line.Split(','); var dataPoint = new DataPoint((int)Convert.ToDouble(data[0]), Convert.ToDouble(data[1])); fileContent.Add(dataPoint); } fileStream.Close(); } return; } public DataPoint GetDataPoint(int i) { return fileContent[i]; } public List<DataPoint> GetDataPoints() { return fileContent; } public void Dispose() { if (isDisposed) return; System.Console.WriteLine($"[C# - {dto.ToUnixTimeMilliseconds().ToString()}] Disoposing Obj {objectId}"); isDisposed = true; } public void Close() { System.Console.WriteLine("Dummy things That I have to do before closing"); } } }
异常输出
使用调试器运行Python主程序时一切正常(时间戳顺序正确),但不使用调试器时必然出现以下错误:
Dummy things That I have to do before closing Done! [C# - 1698683227427] Disoposing Obj 26765710 [C# - 1698683227427 ] Deleting 26765710 [FileLoader - 1698683227.5009353] Freeing memory! Exception ignored in: <function FileLoader.__del__ at 0x00000158F00920D0> Traceback (most recent call last): File "FileLoader.py", line 30, in __del__ TypeError: 'MethodObject' object is not callable
问题分析与修复方案
问题根源
核心矛盾是Python和CLR的垃圾回收执行顺序不匹配:
- 程序退出阶段,Pythonnet会优先触发CLR的资源清理流程,导致C#对象先于Python的
FileLoader对象被回收。 - Python的
__del__方法执行时,self.dataLoader对应的C#对象已失效,其Close方法引用变为不可调用的无效状态,从而抛出异常。
修复方案
方案1:使用Python上下文管理器(推荐)
放弃依赖__del__的隐式回收,改用with语句显式控制资源生命周期,确保C#方法在对象有效时被调用:
修改Python类代码:
import pythonnet from pythonnet import load load("netfx") import clr try: clr.AddReference('FileManager.Library') import FileManager.Library except System.IO.FileNotFoundException: print("[File loader] DLL not found!") class FileLoader: def __init__(self, filePath): self.dataLoader = FileManager.Library.FileReader(filePath) self.isClosed = False def load_data(self): return self.dataLoader.GetDataPoints() def close(self): if not self.isClosed: self.dataLoader.Close() self.isClosed = True def __enter__(self): return self def __exit__(self, exc_type, exc_val, exc_tb): self.close()
修改主程序:
from FileManager.FileLoader import FileLoader if __name__ == "__main__": with FileLoader("data.csv") as fl: data = fl.load_data() print("Done!")
方案2:显式触发资源释放与GC
在主程序中显式调用close方法,并触发Python GC,确保C#方法在CLR回收前执行:
修改主程序:
from FileManager.FileLoader import FileLoader import gc if __name__ == "__main__": fl = FileLoader("data.csv") try: data = fl.load_data() print("Done!") finally: fl.close() gc.collect()
方案3:优化C#类的鲁棒性
修改C#类的Close方法,确保对象Dispose后调用仍安全:
public void Close() { if(isDisposed) return; System.Console.WriteLine("Dummy things That I have to do before closing"); }
内容的提问来源于stack exchange,提问作者Riccardo Agazzotti
相关产品推荐
相关产品推荐

