我继承了一个现有的应用程序来维护并遇到障碍:它一旦完成就不会从内存中释放它。我在这里提取了相关代码。这是一种使用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();
}
}
如果我在if
之后注释掉excel = new Excel.Application()...
语句,该方法可以正常工作并处理excel对象。但是当包含if (excel == null)
测试时,excel对象不会从内存中释放出来。我一直想弄清楚为什么,我很难过。我知道运行时可以自动在com对象周围放置一个包装器(即使用超过“2点”访问com对象时)。但只是检查一个com对象是否等于null似乎不是运行时这样做的逻辑位置,如果确实是这样的话。
那么如何正确检查com对象是否为空?还有其他我想要检查的地方,例如在尝试关闭工作簿并退出Excel时在catch块内部。但是,我现在担心检查catch块中的excel object == null
是否会生成一个不可见的包装器,所以我已经使用了嵌套的try..catch块。但这也感觉不对。
问题是,发生了什么,以及如果我的方法不正确,检查Excel com对象是否正确实例化的最佳做法是什么。
答案 0 :(得分:1)
尝试将垃圾收集器调用移动到包装器方法。这样,当您调用它时,您可以确保所有自动创建的引用都超出范围。
Marshal.ReleaseComObject
另外,根据我的经验,您不需要调用$movie = Movie::whereIn('id', function($query) use ($id, $date) {
$query->select(DB::raw('session.movie_id'))
->from('session')
->whereRaw('session.id = ? and session.date = ? array($id, $date));
})
->get();
,如果您释放了所有引用,则垃圾收集器会释放所有内容。也不需要睡觉。无论如何,GC呼叫都在阻塞。