最近写了个软件,要求读取两个Excel文件的数据,分析之后将新数据保存到新的文件中。软件编成后,整个操作在20秒内就完成了。但是退出后会发现任务管理器里有几个Excel.exe的进程,如果运行多次,这个进程数量也会慢慢地增加。
起初,读取和写入数据是分别创建了3个Excel.Application的对象。这样每运行一次,进程里会多出3个Excel.exe的进程。第一次改进是声明一个窗口级的私有变量,在构造函数中初始化变量对象,在窗口关闭事件里清理Excel.Application对象。第一次按网上的方法去执行发现没有效果,后来查了下微软的官方文档才搞清楚是怎么回事。先贴出正确的运行代码:
public partial class MainWindow
{
...
private Excel.Application excelApp;
public string FirstExcle { get; set; }
...
public MainWindow()
{
...
excelApp = new Excel.Application();
}
...
private void ReadFirstExcel()
{
var myWorkbook = excelApp.Workbooks.Open(FirstExcel); // 注意对象的创建顺序
var tempData = myWorkbook.Worksheets[1].Range["A:A"];
var count = excelApp.WorksheetFunction.CountA(tempData); // 此变量不是COM对象
// 分别读取各列数据
var first = myWorkbook.Worksheets[1].Range[$"A2:A{count}"];
var second = myWorkbook.Worksheets[1].Range[$"C2:C{count}"];
var third = myWorkbook.Worksheets[1].Range[$"D2:D{count}"];
var fourth = myWorkbook.Worksheets[1].Range[$"F2:F{count}"];
var fifth = myWorkbook.Worksheets[1].Range[$"J2:J{count}"];
...
// 不保存退出Excel
myWorkbook.Saved = true;
myWorkbook.Close();
// Marshal.ReleaseComObject(fifth); // 对象的释放顺序与创建顺序相板
// Marshal.ReleaseComObject(fourth); // 对创建的每个COM对象都要调用一次
// Marshal.ReleaseComObject(third);
// Marshal.ReleaseComObject(second);
// Marshal.ReleaseComObject(first);
// Marshal.ReleaseComObject(tempData);
// Marshal.ReleaseComObject(myWorkbook);
// fifth = null; // 使用ReleaseComObject()函数必须得有这些赋空值的语句
// fourth = null;
// third = null;
// second = null;
// tempData = null;
// myWorkbook = null;
Marshal.FinalReleaseComObject(fifth); // 此函数对顺序没有严格的要求
Marshal.FinalReleaseComObject(fourth); // 推荐使用FinalReleaseComObject()函数
Marshal.FinalReleaseComObject(third);
Marshal.FinalReleaseComObject(second);
Marshal.FinalReleaseComObject(first);
Marshal.FinalReleaseComObject(tempData);
Marshal.FinalReleaseComObject(myWorkbook);
}
...
private void Window_Closing(object sender, CancelEventArgs e)
{
if (excelApp != null)
{
excelApp.Quit();
Marshal.FinalReleaseComObject(excelApp);
System.GC.Collect(); // 最后调用垃圾收集器进行清理
// Marshal.ReleaseComObject(excelApp);
// excelApp = null; // 此语句需要配合ReleaseComObject()函数
}
}
}
在C#里,清理COM资源有两个函数,分别是
public static int Marshal.ReleaseComObject(object ComObject);
public static int Marshal.FinalReleaseComObject(object ComObject);
ReleaseComObject(comObject)相当于调用底层的comObject->Release(),对调用顺序有严格的要求,刚开始我就因为调用顺序错误才导致没有效果。它的调用顺序最好按栈的方式调用,先创建的对象最后释放——也就是后进先出的方式。
FinalReleaseComObject(comObject)是托管代码对ReleaseComObject(comObject)的封装,相当于
while (ReleaseComObject(comObject) != 0);
此函数对顺序没有严格的要求,只要保证程序里对每个com对象调用一次,然后将变量设置为null,最后调用System.GC.Collect()方法即可清理COM资源。微软建议优先使用第二个函数,而第一个函数是让对性能有绝对要求的人使用的。它要求调用顺序一定要正确,否则底层的COM对象超出作用域就释放不了了。
这几天对这两个函数仔细测试了一下,发现在实际程序中使用
Marshal.ReleaseComObject(comObject);
函数没
Marshal.FinalReleaseComObject(comObject);
稳定。使用前者,还得将其comObject赋值为null,但有时候编译后仍然不能完全释放资源,任务管理器里还会存在。使用后者,则不会出现此现象,comObject不需要赋值为null,资源能完全释放掉。看来微软建议的函数还是有其优势的。