问题
I have been trying to import data from an excel sheet into a DataTable
. The problem is that after i finish, excel would not terminate the process. I do not want to terminate by process name using System.Diagnostics
as that approach would terminate all excel instances rather than the one that was created by the application. I know that this question was posted over here before multiple times, but none of the solutions seem to work for me. I am probably missing something and can not see it.
below is my code:
private void importToolStripMenuItem_Click(object sender, EventArgs e)
{
openFileDialog1.Filter = "Excel WorkBooks (*.xlsx)|*.xlsx";
openFileDialog1.FilterIndex = 1;
openFileDialog1.Multiselect = false;
if(openFileDialog1.ShowDialog() == DialogResult.OK)
{
string empRange = "D4";
string emptxid = "K4";
object oMissing = System.Reflection.Missing.Value;
string path = openFileDialog1.FileName;
oExcel.Application xlApp;
oExcel.Workbooks xlWorkBooks;
oExcel.Workbook xlWorkBook;
oExcel.Sheets xlSheets;
oExcel.Worksheet xlWorkSheetDATA;
oExcel.Worksheet xlWorkSheetEMP;
oExcel.Range range;
xlApp = new oExcel.Application();
xlWorkBooks = xlApp.Workbooks;
xlWorkBook = xlWorkBooks.Open(path);
xlSheets = xlWorkBook.Worksheets;
xlWorkSheetDATA = (oExcel.Worksheet)xlSheets.get_Item(DATASheetName);
xlWorkSheetEMP = (oExcel.Worksheet)xlSheets.get_Item(EMPSheetName);
xlWorkSheetEMP.Activate();
range = xlWorkSheetEMP.get_Range(empRange, empRange);
xlWorkSheetDATA.Activate();
range = xlWorkSheetDATA.get_Range(emptxid, emptxid);
xlApp.DisplayAlerts = false;
xlWorkSheetDATA.Columns.ClearFormats();
xlWorkSheetDATA.Rows.ClearFormats();
int iTotalColumns = xlWorkSheetDATA.UsedRange.Columns.Count;
int iTotalRows = xlWorkSheetDATA.UsedRange.Rows.Count;
xlApp.Visible = true;
DataTable dt = new DataTable();
addColumns(iTotalColumns, dt);
insertIntoDataTable(iTotalRows, dt, path);
//clean-up
System.Runtime.InteropServices.Marshal.ReleaseComObject(range);
range = null;
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkSheetEMP);
xlWorkSheetEMP = null;
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkSheetDATA);
xlWorkSheetDATA = null;
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlSheets);
xlSheets = null;
xlWorkBook.Close(false);
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkBook);
xlWorkBook = null;
xlWorkBooks.Close();
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlWorkBooks);
xlWorkBooks = null;
xlApp.Quit();
System.Runtime.InteropServices.Marshal.ReleaseComObject(xlApp);
xlApp = null;
}
}
回答1:
Write a method to release the objects such as this example
private void ReleaseOfficeObject(object o)
{
Marshal.ReleaseComObject(o);
GC.Collect();
GC.WaitForPendingFinalizers();
Marshal.FinalReleaseComObject(o);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
then to call it
ReleaseOfficeObject(range);
ReleaseOfficeObject(xlWorkSheetDATA);
ReleaseOfficeObject(xlSheets);
xlWorkBook.Close(false);
ReleaseOfficeObject(xlWorkBook);
xlWorkBook = null;
xlWorkBooks.Close();
ReleaseOfficeObject(xlWorkBooks);
xlWorkBooks = null;
xlApp.Quit();
ReleaseOfficeObject(xlApp);
回答2:
You never need to call Marshal.ReleaseComObject
in this context. The runtime is perfectly able to keep track of COM objects and release them when they are no longer referenced. Calling Marshal.ReleaseComObject
is a confusing anti-pattern that sadly even some Microsoft documentation mistakenly suggests. You also don't have to set local variables to null - the references in local variables will be out of scope when the method completes.
You do have to be careful with this kind of code in debug builds. References in a method are artificially kept alive until the end of the method so that they will still be accessible in the debugger. This means your local xlApp
variable might not be cleaned up by calling the GC inside that method.
You should run the garbage collection twice, reference cycles could be broken in the first collection, but you need a second collection to be sure all cleanup is done for those references.
To avoid this issue, you might follow a pattern like this:
private void importToolStripMenuItem_Click(object sender, EventArgs e)
{
openFileDialog1.Filter = "Excel WorkBooks (*.xlsx)|*.xlsx";
openFileDialog1.FilterIndex = 1;
openFileDialog1.Multiselect = false;
if(openFileDialog1.ShowDialog() == DialogResult.OK)
{
string path = openFileDialog1.FileName;
// Call another method that wraps all the Excel COM stuff
// That prevents debug builds from confusing the lifetime of COM references
DoExcelStuff(path);
// When back here, all the Excel references should be out of scope
// Run the GC (twice) to clean up all COM references
// (This can be pulled out into a helper method somewhere)
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
GC.WaitForPendingFinalizers();
}
}
private void DoExcelStuff(string path)
{
// All your Excel COM interop goes here
string empRange = "D4";
string emptxid = "K4";
object oMissing = System.Reflection.Missing.Value;
// ... variable declarations
xlApp = new oExcel.Application();
// More xlApp, workbooks etc...
// Done - tell Excel to close
xlApp.Quit();
// No need for Marshal.ReleaseComObject(...)
// or setting variables to null here!
}
来源:https://stackoverflow.com/questions/31524103/excel-process-will-not-terminate-with-excel-introp