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

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的垃圾回收执行顺序不匹配:

  1. 程序退出阶段,Pythonnet会优先触发CLR的资源清理流程,导致C#对象先于Python的FileLoader对象被回收。
  2. 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 04:27:11