如果我检查它是否为空,为什么 Excel com 对象不会从内存中释放?
Why will Excel com object not release from memory if I check it for null?
我继承了一个现有的应用程序进行维护,但遇到了一个障碍:它不会在完成后从内存中释放 Excel 对象。我在这里提取了相关代码。这是一种使用 COM Interop 创建 Excel 工作簿的方法:
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
...
private void CreateWorkbook(string workingDirectory, string fileName, object[,] cellValues)
{
Excel.Application excel = null;
Excel.Workbooks workbooks = null;
Excel.Workbook workbook = null;
Excel.Sheets worksheets = null;
Excel.Worksheet worksheet = null;
Excel.Range startRange = null;
Excel.Range endRange = null;
Excel.Range selectedRange = null;
try
{
excel = new Excel.Application() { DisplayAlerts = false, Visible = false, ScreenUpdating = false, EnableAutoComplete = false };
// Statement which stops the excel instance exiting:
if(excel == null)
{
MessageBox.Show("Excel could not be started, please check your machine has office 2013 installed.");
return;
}
workbooks = excel.Workbooks;
workbook = workbooks.Add(Type.Missing);
worksheets = workbook.Sheets;
worksheet = (Excel.Worksheet)worksheets[1];
startCellRange = (Excel.Range)worksheet.Cells[1,1];
endCellRange = (Excel.Range)worksheet.Cells[1 + cellValues.GetLength(0), 1 + cellValues.GetLength(1)];
selectedCells = worksheet.Range[startCellRange, endCellRange];
selectedCells.Value2 = cellValues;
workbook.SaveAs(workingDirectory + fileName, Excel.XlFileFormat.xlOpenXMLWorkbook, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Excel.XlSaveAsAccessMode.xlExclusive, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
workbook.Close(true, Type.Missing, Type.Missing);
excel.Quit();
}
catch(Exception ex)
{
try
{
// Catch methods should ideally never throw exceptions, so try close the workbook and quit the excel instance in a try/catch block:
workbook.Close(false, Type.Missing, Type.Missing);
excel.Quit();
}
catch { }
MessageBox.Show("An error was encountered while trying to create the excel workbook.\n" + ex.Message + (ex.InnerException == null ? "" : "\n" + ex.InnerException.Message));
}
finally
{
// Now clean up the com interop stuff:
// ===================================
if (Marshal.IsComObject(rangeFont)) Marshal.ReleaseComObject(rangeFont);
if (Marshal.IsComObject(rangeBorders)) Marshal.ReleaseComObject(rangeBorders);
if (Marshal.IsComObject(selectedRange)) Marshal.ReleaseComObject(selectedRange);
if (Marshal.IsComObject(endRange)) Marshal.ReleaseComObject(endRange);
if (Marshal.IsComObject(startRange)) Marshal.ReleaseComObject(startRange);
if (Marshal.IsComObject(worksheet)) Marshal.ReleaseComObject(worksheet);
if (Marshal.IsComObject(worksheets)) Marshal.ReleaseComObject(worksheets);
if (Marshal.IsComObject(workbook)) Marshal.ReleaseComObject(workbook);
if (Marshal.IsComObject(workbooks)) Marshal.ReleaseComObject(workbooks);
if (Marshal.IsComObject(excel)) Marshal.ReleaseComObject(excel);
if (Marshal.IsComObject(excel)) Marshal.FinalReleaseComObject(excel);
System.Threading.Thread.Sleep(100);
GC.Collect();
GC.WaitForPendingFinalizers();
System.Threading.Thread.Sleep(500);
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
如果我在 excel = new Excel.Application()...
之后注释掉 if
语句,该方法工作正常并处理 excel 对象。但是当包含 if (excel == null)
测试时,excel 对象不会从内存中释放。我一直在试图找出原因,但我很困惑。我知道运行时可以自动在 com 对象周围放置一个包装器(即当使用超过“2 个点”访问 com 对象时)。但是,仅仅检查 com 对象是否等于 null 似乎并不是运行时执行此操作的合乎逻辑的地方,如果确实是这样的话。
那么如何正确地检查 com 对象是否为 null?还有其他地方我想做检查,例如在尝试关闭工作簿并退出时在 catch 块内 Excel。但是,我现在担心检查 catch 块内的 excel object == null
是否会形成一个不可见的包装器,所以我使用了嵌套的 try..catch 块。但这也不对。
问题是,这是怎么回事,如果我的方法不正确,检查 Excel com 对象是否正确实例化的最佳实践是什么。
尝试将垃圾收集器调用移至包装器方法。这样您就可以确保在调用它时所有自动创建的引用都超出范围。
private void CreateWorkbookWithCleanup(...)
{
CreateWorkbook(...);
// Yes, we really want to call those two methods twice to make sure all
// COM objects AND all RCWs are collected.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
此外,根据我的经验,您不需要调用 Marshal.ReleaseComObject
,如果您释放所有引用,垃圾收集器会释放所有内容。也不应该需要睡眠。 GC 调用无论如何都会阻塞。
我继承了一个现有的应用程序进行维护,但遇到了一个障碍:它不会在完成后从内存中释放 Excel 对象。我在这里提取了相关代码。这是一种使用 COM Interop 创建 Excel 工作簿的方法:
using System.Runtime.InteropServices;
using Excel = Microsoft.Office.Interop.Excel;
...
private void CreateWorkbook(string workingDirectory, string fileName, object[,] cellValues)
{
Excel.Application excel = null;
Excel.Workbooks workbooks = null;
Excel.Workbook workbook = null;
Excel.Sheets worksheets = null;
Excel.Worksheet worksheet = null;
Excel.Range startRange = null;
Excel.Range endRange = null;
Excel.Range selectedRange = null;
try
{
excel = new Excel.Application() { DisplayAlerts = false, Visible = false, ScreenUpdating = false, EnableAutoComplete = false };
// Statement which stops the excel instance exiting:
if(excel == null)
{
MessageBox.Show("Excel could not be started, please check your machine has office 2013 installed.");
return;
}
workbooks = excel.Workbooks;
workbook = workbooks.Add(Type.Missing);
worksheets = workbook.Sheets;
worksheet = (Excel.Worksheet)worksheets[1];
startCellRange = (Excel.Range)worksheet.Cells[1,1];
endCellRange = (Excel.Range)worksheet.Cells[1 + cellValues.GetLength(0), 1 + cellValues.GetLength(1)];
selectedCells = worksheet.Range[startCellRange, endCellRange];
selectedCells.Value2 = cellValues;
workbook.SaveAs(workingDirectory + fileName, Excel.XlFileFormat.xlOpenXMLWorkbook, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Excel.XlSaveAsAccessMode.xlExclusive, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
workbook.Close(true, Type.Missing, Type.Missing);
excel.Quit();
}
catch(Exception ex)
{
try
{
// Catch methods should ideally never throw exceptions, so try close the workbook and quit the excel instance in a try/catch block:
workbook.Close(false, Type.Missing, Type.Missing);
excel.Quit();
}
catch { }
MessageBox.Show("An error was encountered while trying to create the excel workbook.\n" + ex.Message + (ex.InnerException == null ? "" : "\n" + ex.InnerException.Message));
}
finally
{
// Now clean up the com interop stuff:
// ===================================
if (Marshal.IsComObject(rangeFont)) Marshal.ReleaseComObject(rangeFont);
if (Marshal.IsComObject(rangeBorders)) Marshal.ReleaseComObject(rangeBorders);
if (Marshal.IsComObject(selectedRange)) Marshal.ReleaseComObject(selectedRange);
if (Marshal.IsComObject(endRange)) Marshal.ReleaseComObject(endRange);
if (Marshal.IsComObject(startRange)) Marshal.ReleaseComObject(startRange);
if (Marshal.IsComObject(worksheet)) Marshal.ReleaseComObject(worksheet);
if (Marshal.IsComObject(worksheets)) Marshal.ReleaseComObject(worksheets);
if (Marshal.IsComObject(workbook)) Marshal.ReleaseComObject(workbook);
if (Marshal.IsComObject(workbooks)) Marshal.ReleaseComObject(workbooks);
if (Marshal.IsComObject(excel)) Marshal.ReleaseComObject(excel);
if (Marshal.IsComObject(excel)) Marshal.FinalReleaseComObject(excel);
System.Threading.Thread.Sleep(100);
GC.Collect();
GC.WaitForPendingFinalizers();
System.Threading.Thread.Sleep(500);
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
如果我在 excel = new Excel.Application()...
之后注释掉 if
语句,该方法工作正常并处理 excel 对象。但是当包含 if (excel == null)
测试时,excel 对象不会从内存中释放。我一直在试图找出原因,但我很困惑。我知道运行时可以自动在 com 对象周围放置一个包装器(即当使用超过“2 个点”访问 com 对象时)。但是,仅仅检查 com 对象是否等于 null 似乎并不是运行时执行此操作的合乎逻辑的地方,如果确实是这样的话。
那么如何正确地检查 com 对象是否为 null?还有其他地方我想做检查,例如在尝试关闭工作簿并退出时在 catch 块内 Excel。但是,我现在担心检查 catch 块内的 excel object == null
是否会形成一个不可见的包装器,所以我使用了嵌套的 try..catch 块。但这也不对。
问题是,这是怎么回事,如果我的方法不正确,检查 Excel com 对象是否正确实例化的最佳实践是什么。
尝试将垃圾收集器调用移至包装器方法。这样您就可以确保在调用它时所有自动创建的引用都超出范围。
private void CreateWorkbookWithCleanup(...)
{
CreateWorkbook(...);
// Yes, we really want to call those two methods twice to make sure all
// COM objects AND all RCWs are collected.
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
此外,根据我的经验,您不需要调用 Marshal.ReleaseComObject
,如果您释放所有引用,垃圾收集器会释放所有内容。也不应该需要睡眠。 GC 调用无论如何都会阻塞。