Excel 实例不会在互操作操作后关闭
Excel Instance wont close after Interop Operations
我正在通过合并一系列不同 Excel 工作簿中的第一个 sheet,用 c# 构建一个新的 Excel 工作簿;随后我将新工作簿导出为 PDF。我完成了这项工作,但在方法结束时总是有一个 Excel 实例 运行。
我讨论了同样的问题 ,但设置更简单,更少我可以使用 GC.Collect 命令解决的 Excel 个对象。现在,none 正在运行。
public void CombineWorkBooks()
{
Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
xlApp.DisplayAlerts = false;
xlApp.Visible = false;
Workbooks newBooks = null;
Workbook newBook = null;
Sheets newBookWorksheets = null;
Worksheet defaultWorksheet = null;
// Create a new workbook, comes with an empty default worksheet");
newBooks = xlApp.Workbooks;
newBook = newBooks.Add(XlWBATemplate.xlWBATWorksheet);
newBookWorksheets = newBook.Worksheets;
// get the reference for the empty default worksheet
if (newBookWorksheets.Count > 0)
{
defaultWorksheet = newBookWorksheets[1] as Worksheet;
}
// loop through every line in Gridview and get the path' to each Workbook
foreach (GridViewRow row in CertificadosPresion.Rows)
{
string path = row.Cells[0].Text;
string CertName = CertificadosPresion.DataKeys[row.RowIndex].Value.ToString();
Workbook childBook = null;
Sheets childSheets = null;
// Excel of each line in Gridview
childBook = newBooks.Open(path,Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
childSheets = childBook.Worksheets;
if (childSheets != null)
{
// Build a new Worksheet
Worksheet sheetToCopy = null;
// Only first Worksheet of the Workbook belonging to that line
sheetToCopy = childSheets[1] as Worksheet;
if (sheetToCopy != null)
{
// Assign the Certificate Name to the new Worksheet
sheetToCopy.Name = CertName;
// set PageSetup for the new Worksheet to be copied
sheetToCopy.PageSetup.Zoom = false;
sheetToCopy.PageSetup.FitToPagesWide = 1;
sheetToCopy.PageSetup.FitToPagesTall = 1;
sheetToCopy.PageSetup.PaperSize = Microsoft.Office.Interop.Excel.XlPaperSize.xlPaperA4;
// Copy that new Worksheet to the defaultWorksheet
sheetToCopy.Copy(defaultWorksheet, Type.Missing);
}
System.Runtime.InteropServices.Marshal.ReleaseComObject(sheetToCopy);
childBook.Close(false, Type.Missing, Type.Missing);
}
System.Runtime.InteropServices.Marshal.ReleaseComObject(childSheets);
System.Runtime.InteropServices.Marshal.ReleaseComObject(childBook);
}
//Delete the empty default worksheet
if (defaultWorksheet != null) defaultWorksheet.Delete();
//Export to PDF
newBook.ExportAsFixedFormat(Microsoft.Office.Interop.Excel.XlFixedFormatType.xlTypePDF, @"C:\pdf\" + SALESID.Text + "_CertPres.pdf", 0, false, true);
newBook.Close();
newBooks.Close();
xlApp.DisplayAlerts = true;
DownloadFile(SALESID.Text);
System.Runtime.InteropServices.Marshal.ReleaseComObject(defaultWorksheet);
System.Runtime.InteropServices.Marshal.ReleaseComObject(newBookWorksheets);
System.Runtime.InteropServices.Marshal.ReleaseComObject(newBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(newBooks);
xlApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);
GC.Collect();
GC.WaitForPendingFinalizers();
}
protected void DownloadFile(string Salesid)
{
string path = @"c:\pdf\" + Salesid + "_CertPres.pdf";
byte[] bts = System.IO.File.ReadAllBytes(path);
Response.Clear();
Response.ClearHeaders();
Response.AddHeader("Content-Type", "Application/octet-stream");
Response.AddHeader("Content-Length", bts.Length.ToString());
Response.AddHeader("Content-Disposition", "attachment; filename=" + Salesid + "_CertPres.pdf");
Response.BinaryWrite(bts);
Response.Flush();
Response.End();
}
问题一定与调用下载文件方法有关。我消除了那个调用,并且 Excel 进程被正确关闭。其中一些操作必须保持对其中一个 COM 对象的引用处于打开状态,以便它们无法关闭。通过在 GarbageCollect 之后的最后调用“DownloadFile”,问题就解决了。 (我不太清楚为什么)
我发现有时唯一有用的是 "sledgehammer method"。杀死所有 运行 excel 个实例:
foreach (Process p in Process.GetProcessesByName("EXCEL"))
{
try
{
p.Kill();
p.WaitForExit();
}
catch
{
//Handle exception here
}
}
在我看来,您有一个未清理的参考。可能类似于 'two dot rule' 问题 - 在我看来这是一个愚蠢的规则,因为你不能编写任何像样的代码,因为它很难跟踪。
您可以尝试 Marshal.ReleaseComObject 个 COM 引用,但仍然自找麻烦...
我的建议是尝试使用 VSTO 实现自动化 Excel。这将代表您正确清除您的引用。
在您的方法 DownloadFile 中,您调用
Response.End()
HttpResponse.End 抛出异常(强调我的):
To mimic the behavior of the End method in ASP, this method tries to raise a ThreadAbortException exception. If this attempt is successful, the calling thread will be aborted, [...]
此异常中止您的线程。因此,您所有的 ReleaseComObject,Excel.Quit,GC.Collect 东西 永远不会执行 。
解决方法:不要调用 Response.End
。你可能不需要它。如果需要,您可能需要考虑替代方案 mentioned in the documentation:
This method is provided only for compatibility with ASP—that is, for compatibility with COM-based Web-programming technology that preceded ASP.NET. If you want to jump ahead to the EndRequest event and send a response to the client, it is usually preferable to call CompleteRequest instead.
[...]
The CompleteRequest method does not raise an exception, and code after the call to the CompleteRequest method might be executed
PS:从 Web 应用程序使用 Excel 自动化是 not officially supported by Microsoft。对于未来的开发,您可能需要考虑使用 third-party Excel 库。
我正在通过合并一系列不同 Excel 工作簿中的第一个 sheet,用 c# 构建一个新的 Excel 工作簿;随后我将新工作簿导出为 PDF。我完成了这项工作,但在方法结束时总是有一个 Excel 实例 运行。
我讨论了同样的问题
public void CombineWorkBooks()
{
Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
xlApp.DisplayAlerts = false;
xlApp.Visible = false;
Workbooks newBooks = null;
Workbook newBook = null;
Sheets newBookWorksheets = null;
Worksheet defaultWorksheet = null;
// Create a new workbook, comes with an empty default worksheet");
newBooks = xlApp.Workbooks;
newBook = newBooks.Add(XlWBATemplate.xlWBATWorksheet);
newBookWorksheets = newBook.Worksheets;
// get the reference for the empty default worksheet
if (newBookWorksheets.Count > 0)
{
defaultWorksheet = newBookWorksheets[1] as Worksheet;
}
// loop through every line in Gridview and get the path' to each Workbook
foreach (GridViewRow row in CertificadosPresion.Rows)
{
string path = row.Cells[0].Text;
string CertName = CertificadosPresion.DataKeys[row.RowIndex].Value.ToString();
Workbook childBook = null;
Sheets childSheets = null;
// Excel of each line in Gridview
childBook = newBooks.Open(path,Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing, Type.Missing);
childSheets = childBook.Worksheets;
if (childSheets != null)
{
// Build a new Worksheet
Worksheet sheetToCopy = null;
// Only first Worksheet of the Workbook belonging to that line
sheetToCopy = childSheets[1] as Worksheet;
if (sheetToCopy != null)
{
// Assign the Certificate Name to the new Worksheet
sheetToCopy.Name = CertName;
// set PageSetup for the new Worksheet to be copied
sheetToCopy.PageSetup.Zoom = false;
sheetToCopy.PageSetup.FitToPagesWide = 1;
sheetToCopy.PageSetup.FitToPagesTall = 1;
sheetToCopy.PageSetup.PaperSize = Microsoft.Office.Interop.Excel.XlPaperSize.xlPaperA4;
// Copy that new Worksheet to the defaultWorksheet
sheetToCopy.Copy(defaultWorksheet, Type.Missing);
}
System.Runtime.InteropServices.Marshal.ReleaseComObject(sheetToCopy);
childBook.Close(false, Type.Missing, Type.Missing);
}
System.Runtime.InteropServices.Marshal.ReleaseComObject(childSheets);
System.Runtime.InteropServices.Marshal.ReleaseComObject(childBook);
}
//Delete the empty default worksheet
if (defaultWorksheet != null) defaultWorksheet.Delete();
//Export to PDF
newBook.ExportAsFixedFormat(Microsoft.Office.Interop.Excel.XlFixedFormatType.xlTypePDF, @"C:\pdf\" + SALESID.Text + "_CertPres.pdf", 0, false, true);
newBook.Close();
newBooks.Close();
xlApp.DisplayAlerts = true;
DownloadFile(SALESID.Text);
System.Runtime.InteropServices.Marshal.ReleaseComObject(defaultWorksheet);
System.Runtime.InteropServices.Marshal.ReleaseComObject(newBookWorksheets);
System.Runtime.InteropServices.Marshal.ReleaseComObject(newBook);
System.Runtime.InteropServices.Marshal.ReleaseComObject(newBooks);
xlApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);
GC.Collect();
GC.WaitForPendingFinalizers();
}
protected void DownloadFile(string Salesid)
{
string path = @"c:\pdf\" + Salesid + "_CertPres.pdf";
byte[] bts = System.IO.File.ReadAllBytes(path);
Response.Clear();
Response.ClearHeaders();
Response.AddHeader("Content-Type", "Application/octet-stream");
Response.AddHeader("Content-Length", bts.Length.ToString());
Response.AddHeader("Content-Disposition", "attachment; filename=" + Salesid + "_CertPres.pdf");
Response.BinaryWrite(bts);
Response.Flush();
Response.End();
}
问题一定与调用下载文件方法有关。我消除了那个调用,并且 Excel 进程被正确关闭。其中一些操作必须保持对其中一个 COM 对象的引用处于打开状态,以便它们无法关闭。通过在 GarbageCollect 之后的最后调用“DownloadFile”,问题就解决了。 (我不太清楚为什么)
我发现有时唯一有用的是 "sledgehammer method"。杀死所有 运行 excel 个实例:
foreach (Process p in Process.GetProcessesByName("EXCEL"))
{
try
{
p.Kill();
p.WaitForExit();
}
catch
{
//Handle exception here
}
}
在我看来,您有一个未清理的参考。可能类似于 'two dot rule' 问题 - 在我看来这是一个愚蠢的规则,因为你不能编写任何像样的代码,因为它很难跟踪。
您可以尝试 Marshal.ReleaseComObject 个 COM 引用,但仍然自找麻烦...
我的建议是尝试使用 VSTO 实现自动化 Excel。这将代表您正确清除您的引用。
在您的方法 DownloadFile 中,您调用
Response.End()
HttpResponse.End 抛出异常(强调我的):
To mimic the behavior of the End method in ASP, this method tries to raise a ThreadAbortException exception. If this attempt is successful, the calling thread will be aborted, [...]
此异常中止您的线程。因此,您所有的 ReleaseComObject,Excel.Quit,GC.Collect 东西 永远不会执行 。
解决方法:不要调用 Response.End
。你可能不需要它。如果需要,您可能需要考虑替代方案 mentioned in the documentation:
This method is provided only for compatibility with ASP—that is, for compatibility with COM-based Web-programming technology that preceded ASP.NET. If you want to jump ahead to the EndRequest event and send a response to the client, it is usually preferable to call CompleteRequest instead.
[...]
The CompleteRequest method does not raise an exception, and code after the call to the CompleteRequest method might be executed
PS:从 Web 应用程序使用 Excel 自动化是 not officially supported by Microsoft。对于未来的开发,您可能需要考虑使用 third-party Excel 库。