问题 C#Excel自动化导致Excel内存泄漏


我正在尝试将C#与COM Interop库一起使用来打开一组非常繁重的Excel工作簿。我必须使用C#,因为我还需要启动宏,移动一些单元格,并启动我公司使用的自定义excel-add-in。

然后我的程序退出,打开工作簿,每个工作簿都在一个单独的excel实例中。我不希望在程序退出时关闭工作簿。

问题是,当我的C#程序退出时,随着时间的推移,excel工作簿逐渐消耗更多内存,直到他们从原始的500 MB消耗3.5 GB的内存。

我曾经手工打开工作簿,这些工作表从来没有消耗过那么多的记忆。一旦我开始使用C#打开它们,由于极端的内存使用,它们开始破坏。我的理论是,不知何故,当我与COM Excel对象交互时,我创建了一个内存泄漏。

以下是我的原始代码:

using Excel = Microsoft.Office.Interop.Excel;
...
excelApp = new Excel.Application();
excelApp.Visible = true;
excelApp.Workbooks.Open(filename, misValue, misValue, misValue, misValue, misValue,
               true, misValue, misValue, misValue, misValue, misValue, misValue, misValue, misValue);
excelApp.Calculation = Excel.XlCalculation.xlCalculationAutomatic;

我读到了你需要如何使用Marshal来释放用途,所以我现在正在尝试以下代码,但没有简单的方法来测试它,除了打开所有工作表并查看它们是否消耗了太多数据。

            excelApp = new Excel.Application();
            excelApp.Visible = true;
            Excel.Workbooks currWorkbooks = excelApp.Workbooks;
            Excel.Workbook currWorkbook = currWorkbooks.Open(filename, misValue, misValue, misValue, misValue, misValue,
               true, misValue, misValue, misValue, misValue, misValue, misValue, misValue, misValue);
            //excelApp.Calculation = Excel.XlCalculation.xlCalculationAutomatic;

            int x = Marshal.ReleaseComObject(currWorkbook);
            currWorkbook = null;

            int y = Marshal.ReleaseComObject(currWorkbooks);
            currWorkbooks = null;

5450
2017-11-20 22:46


起源

你已经将currWorkbook设置为null ..那你为什么要这样做两次呢。还有Mashal.ReleaseComObject(currWorkBook)你需要的只是为什么你要分配一个Int来检查对象是否已被释放..你是否得到任何错误.. - MethodMan
我没有收到任何错误。问题是excel工作簿以这种方式启动,慢慢消耗越来越多的内存。 - user804649
你可能想看看EPPlus(epplus.codeplex.com)。我不确定它有什么类型的宏支持(它确实提到了VBA作为一个功能),但总的来说,我发现EPPlus比Excel Interop更高效,更不容易出错。 - devuxer


答案:


使用MS Office COM Interop库时,我遇到了一些避免内存泄漏的事情:

首先,“不要使用两个点”是记住它的最佳方式,但基本上,总是为新变量分配一个新的COM对象引用,即使Intellisense鼓励它,也不要对成员进行链式调用。链式调用会在后台执行一些阻止.NET框架正确释放的内容。以下是我用于启动Excel报告的一些代码:

//use vars for every COM object so references don't get leftover
//main Excel app
var excelApp = new Application();
var workbooks = excelApp.Workbooks;

//workbook template
var wbReport = workbooks.Add(@"C:\MyTemplate.xltx");

//Sheets objects for workbook
var wSheetsReport = wbReport.Sheets;
var wsReport = (Worksheet)wSheetsReport.get_Item("Sheet1");

其次,打电话 Marshal.ReleaseComObject() 对于以创建的相反顺序创建的每个变量,并在执行此操作之前调用几个垃圾收集方法:

//garbage collector
GC.Collect();
GC.WaitForPendingFinalizers();

//cleanup
Marshal.ReleaseComObject(wsReport);
Marshal.ReleaseComObject(wSheetsReport);
Marshal.ReleaseComObject(wbReport);
Marshal.ReleaseComObject(workbooks);
Marshal.ReleaseComObject(excelApp);

每次使用Excel时都使用这个方案已经解决了我的记忆问题,虽然它很乏味和悲伤,我们不能像我们习惯的那样使用链式成员。


16
2017-11-20 23:07



有关详细信息,请参阅此Q / A: stackoverflow.com/questions/158706/...。请注意,后面的一些答案可能比接受的答案更好。 - devuxer
啊,那可能是我养成习惯的地方..我记不起在很久以前我读过它的地方了,所以我刚回答......这个链接可能是最好的阅读内容。 :) - Andy Raddatz
+1我曾在几个地方看过使用“两个点”并不好,但我不知道为什么。现在我做! - Sid Holland
这没用。在我的C#程序EXITS之后,我仍然在使用Excel中的内存使用量增加。每次打开新的excelApp之前,我都会在发布工作簿,工作簿,最后是excelApp之前调用GC.Collect()和GC.WaitForPendingFinalizers两次。我也从不对任何COM对象使用两个句点。 - user804649
我想我没想到之后你想让工作簿保持开放......你是否曾经从命令行开始并从中运行一个宏来探索 Workbook_Open 方法?如果您开始使用C#进行细胞操作,这只会是一个问题... stackoverflow.com/questions/2050505/... - Andy Raddatz