如何从 C# 应用程序中正确清理 Excel 互操作对象?

Posted

技术标签:

【中文标题】如何从 C# 应用程序中正确清理 Excel 互操作对象?【英文标题】:How do I properly clean up Excel interop objects from an C# application? 【发布时间】:2020-02-10 16:20:28 【问题描述】:

这个问题已经被问过很多次了,例如:

How do I properly clean up Excel interop objects?

How to dispose Interop Excel Application and workbook correctly?

Why does Microsoft.Office.Interop.Excel.Application.Quit() leave the background process running?

How can I dispose my Excel Application

但 Hans Passent 对以下问题的回答让我相信它们已经过时和/或根本不正确:

Clean up Excel Interop Objects with IDisposable

Understanding garbage collection in .NET

所以,我的问题是: 如何清理我的 Excel 互操作对象,以便及时释放所有托管和非托管 Excel 资源(即当内存压力触发垃圾回收时)?

处于发布模式? 处于调试模式(如果我们关心的话)?

在释放模式下:

让所有托管的 Excel 互操作对象超出范围就足够了吗?

我也需要调用 excelApp.Quit() 吗?

非托管堆上的内存压力会触发垃圾回收吗?即我是否还需要致电:

GC.Collect();
GC.WaitForPendingFinalizers();

确保我的托管应用不会耗尽内存? 我是否需要调用:System.Runtime.InteropServices.Marshal.FinalReleaseComObject(managedExcelObject)?

除非您已阅读并理解 Hans Passent 的回答,否则请不要回答此问题。

【问题讨论】:

您会发现曾经使用过 COM 的每个人都理解这些答案,并且可以告诉您同样的答案。大多数情况下的解决方案是尽可能使用 Office 互操作。使用像 Epplus 这样的库来创建真正的 xlsx 文件。 我同意。如果不需要,请不要使用互操作。请改用Open XML SDK 之类的东西。 你真的写过你的程序并证明这里突出显示的项目有问题吗? 一直在同一条船上,互操作的东西总是比它的价值更令人头疼。如果您只是想将工作表加载到内存中,ExcelDataReader 是另一个值得一看的地方。 @Robert Harvey 我最初计划用 Java 编写我的应用程序。这篇文章:***.com/questions/38913412/… 让我相信 Apache POI 或 OpenXML 对图表的支持不是很好。因此,我选择了 C# 和 Excel 互操作,因为我相信它是完整且易于使用的——而且确实如此。我的应用是一次性为我捕获的 csv 数据生成一堆(150 多个)图表。我真的不在乎它是否在完成后泄漏。这个问题真的是为了拓宽我对这个问题的理解。 【参考方案1】:

随着我对 C# Excel 互操作的使用变得越来越复杂,在我关闭应用程序后,我开始在任务管理器中运行“Microsoft Office Excel(32 位)”对象的无头副本。我没有发现 voodoo Marshal.ReleaseComObject() 和 GC.Collect() 的组合可以完全消除它们。我终于删除了所有的巫毒代码,并听从了 Hans Passent 的建议。在大多数情况下,当应用程序关闭时,我可以使用以下模式终止它们:

using System;
using System.IO;
using excel = Microsoft.Office.Interop.Excel;

namespace ExcelInterop 
    static class Program 
        // Create only one instance of excel.Application(). More instances create more Excel objects in Task Manager.
        static excel.Application ExcelApp  get; set;  = new excel.Application();

        [STAThread]
        static int Main() 
            try 
                ExcelRunner excelRunner = new ExcelRunner(ExcelApp)
                // do your Excel interop activities in your excelRunner class here
                // excelRunner MUST be out-of-scope when the finally clause executes
                excelRunner = null;  // not really necessary but kills the only reference to excelRunner 
             catch (Exception e) 
                // A catch block is required to ensure that the finally block excutes after an unhandled exception
                // see: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/try-finally
                Console.WriteLine($"ExcelRunner terminated with unhandled Exception: 'e.Message'");
                return -1;
             finally 
                // this must not execute until all objects derived from 'ExcelApp' are out of scope
                if (ExcelApp != null) 
                    ExcelApp.Quit();
                    ExcelApp = null;
                    GC.Collect();
                    GC.WaitForPendingFinalizers();
                
            
            Console.WriteLine("ExcelRunner terminated normally");
            return 0;
        
    

在我的 ExcelRunner 课程中,我将数百个 csv 文件读入 excel 工作簿,并创建了数十个带有表格和图表的 .xlsx 文件。我只创建了一个 Microsoft.Office.Interop.Excel.Application() 实例并一遍又一遍地重用它。更多实例意味着任务管理器中运行的更多“Microsoft Office Excel”对象需要清理。

请注意,finally 子句必须执行以摆脱无头 Excel 对象。上面的模式可以处理大多数应用程序关闭情况(包括大多数由未处理异常引起的中止 - 但请参阅Does the C# "finally" block ALWAYS execute?)。当您从 VS 调试器(Shift-F5 或工具栏上的红色“停止调试”方块)中止应用程序时,会发生一个值得注意的异常。如果您从调试器中止应用程序,finally 子句不会执行 not 并且 Excel 对象会继续运行。这很不幸,但我没有找到解决办法。

我在 Visual Studio 2019 和 .NET Framework 4.7.2 中使用 Excel 2007 互操作和 Excel 2016 互操作对此进行了测试。

【讨论】:

以上是关于如何从 C# 应用程序中正确清理 Excel 互操作对象?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 Excel office 对象会留下 EXCEL.exe 进程? [复制]

如何使用 oledb 在 c# 中将下拉列数据插入到 excel 中

为啥办公室 PIA 不能正确安装到 GAC?

在使用 c# 从 excel 中读取大于 12 位的数字时,数字被转换为零

正确关闭 C# excel COM 对象

如何在wpf c#中从数据表导出到excel文件