如何从 C# 应用程序中正确清理 Excel 互操作对象?
How do I properly clean up Excel interop objects from an C# application?
这个问题已经被问过多次,例如:
How do I properly clean up Excel interop objects?
How to dispose Interop Excel Application and workbook correctly?
How can I dispose my Excel Application
但是 Hans Passent 对以下问题的回答让我相信它们已经过时 and/or 根本不正确:
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 的回答,否则请勿回答此问题。
随着我对 C# Excel 互操作的使用越来越复杂,我在关闭应用程序后开始在任务管理器中获得 'Microsoft Office Excel (32 bit)' 对象 运行 的无头副本。我发现没有 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 class 中,我正在将数百个 csv 文件读入 excel 工作簿,并创建数十个包含表格和图表的 .xlsx 文件。我只创建了一个 Microsoft.Office.Interop.Excel.Application() 实例并一遍又一遍地重复使用它。更多实例意味着任务管理器中需要清理的 'Microsoft Office Excel' 个对象 运行。
请注意,finally 子句 必须 执行才能删除无头 Excel 对象。上面的模式可以处理大多数应用程序关闭情况(包括大多数由未处理的异常引起的中止 - 但请参阅 Does the C# "finally" block ALWAYS execute?)。当您从 VS 调试器(Shift-F5 或工具栏上的红色 'Stop Debugging' 方块)中止应用程序时,会发生一个值得注意的异常。如果您从调试器中止应用程序,finally 子句会 not 执行并且 Excel 对象继续运行。这很不幸,但我找不到解决办法。
我在 Visual Studio 2019 和 .NET Framework 4.7.2 中使用 Excel 2007 互操作和 Excel 2016 互操作对此进行了测试。
这个问题已经被问过多次,例如:
How do I properly clean up Excel interop objects?
How to dispose Interop Excel Application and workbook correctly?
How can I dispose my Excel Application
但是 Hans Passent 对以下问题的回答让我相信它们已经过时 and/or 根本不正确:
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 的回答,否则请勿回答此问题。
随着我对 C# Excel 互操作的使用越来越复杂,我在关闭应用程序后开始在任务管理器中获得 'Microsoft Office Excel (32 bit)' 对象 运行 的无头副本。我发现没有 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 class 中,我正在将数百个 csv 文件读入 excel 工作簿,并创建数十个包含表格和图表的 .xlsx 文件。我只创建了一个 Microsoft.Office.Interop.Excel.Application() 实例并一遍又一遍地重复使用它。更多实例意味着任务管理器中需要清理的 'Microsoft Office Excel' 个对象 运行。
请注意,finally 子句 必须 执行才能删除无头 Excel 对象。上面的模式可以处理大多数应用程序关闭情况(包括大多数由未处理的异常引起的中止 - 但请参阅 Does the C# "finally" block ALWAYS execute?)。当您从 VS 调试器(Shift-F5 或工具栏上的红色 'Stop Debugging' 方块)中止应用程序时,会发生一个值得注意的异常。如果您从调试器中止应用程序,finally 子句会 not 执行并且 Excel 对象继续运行。这很不幸,但我找不到解决办法。
我在 Visual Studio 2019 和 .NET Framework 4.7.2 中使用 Excel 2007 互操作和 Excel 2016 互操作对此进行了测试。