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

12

我正在尝试使用COM互操作库的C#来打开一组非常大的Excel工作簿。我必须使用C#,因为我还需要启动宏,移动一些单元格并启动公司使用的自定义Excel插件。

然后我的程序退出,留下了打开的工作簿,每个都在单独的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;

你已经将currWorkbook设置为null了,那你为什么还要再做一遍呢。而且Mashal.ReleaseComObject(currWorkBook)就足够了,你为什么要尝试分配一个Int来检查对象是否被释放了呢?你有遇到任何错误吗? - MethodMan
我没有收到任何错误信息。问题是以这种方式启动的Excel工作簿会慢慢消耗更多的内存。 - user804649
你可能想要查看EPPlus(http://epplus.codeplex.com/)。我不确定它有什么类型的宏支持(它确实提到了VBA作为一个特性),但总的来说,我发现EPPlus比Excel Interop更高效、更少出问题。 - devuxer
1个回答

18
使用MS Office COM互操作库时,有几个需要避免内存泄漏的事项:
首先,“不要使用两个点”是最好的方式去记住它,基本上,总是将新的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时都解决了我的内存问题,虽然这很繁琐,但我们无法像以前那样使用链式成员。

5
更多详情请参见这个优秀的问答:https://dev59.com/33VC5IYBdhLWcg3w4VNy。请注意,一些后来的答案可能比被接受的答案更好。 - devuxer
啊,那可能是我养成这些习惯的地方...我记不得我在哪里读过它,因为很久以前了,所以我就回答了...那个链接可能是最好阅读的东西。 :) - Andy Raddatz
+1 我在多个地方读到使用“两个点”不好,但我不知道为什么。现在我知道了! - Sid Holland
1
这个不起作用。在我的C#程序退出后,我仍然经历着Excel中内存使用量的增加。在释放工作簿、工作簿和最终的excelApp之前,我调用了两次GC.Collect()和GC.WaitForPendingFinalizers,每次在打开新的excelApp之前。我也从未在任何COM对象中使用两个句点。 - user804649
我想我没有意识到您希望在之后保留工作簿打开状态... 您是否尝试过从命令行打开并从“Workbook_Open”方法运行宏?只有在您坚持使用C#进行单元格操作时才会出现此问题... https://dev59.com/fHI-5IYBdhLWcg3wBjhR - Andy Raddatz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接