透過.NET程式操作Excel的注意事項
2 |
【個案】某支開啟Excel進行作業的.NET排程程式,定期排程執行時遇到錯誤,留下一堆無主excel.exe。
用以下程式示範:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Excel;
namespace RunExcelBadWay
{
class Program
{
static void Main(string[] args)
{
try
{
Application excel = new Application();
var wrkBook = excel.Workbooks.Add();
var sheet = wrkBook.Sheets.Add();
sheet.Cells(1, 1).Value = "ABC";
wrkBook.SaveAs(@"d:\" + Guid.NewGuid() + ".xlsx");
wrkBook.Close();
excel.Workbooks.Close();
excel.Quit();
Console.WriteLine("Done");
}
catch (Exception ex)
{
Console.WriteLine("Error:" + ex.Message);
}
}
}
}
程式貌似正常,執行結果無誤。但做個小實驗,把存檔路徑改成wrkBook.SaveAs(@"d:\PathNotThere\" + Guid.NewGuid() + ".xlsx");,故意指向不存在的目錄,接著連續執行程式12次,打開Task Manager:
哦哦,冒出12個桌面看不到摸不著的背景EXCEL.EXE,記憶體用量卻持續跳動,是會繼續吃CPU吃RAM的僵屍嗎?
Excel屬於Unmanaged程式,所佔用資源不會在.NET程式結束時自動回收。過去處理其他如FileStream、DbConnection等Unmanaged資源,用using即可搞定,但Excel Application未實做IDisposable介面,無法用using打發。在處理上,我們需要加入finally區段明確清理及關閉Excel,確保任何情況下Excel程式都會確實關閉釋放資源。更進一步,可參考MSDN Code Sample建議:
... Clean up the unmanaged COM resource. To get Excel terminated rightly, we need to call Marshal.FinalReleaseComObject() on each COM object we used ...
意思是程式過程使用過的所有COM+物件,包含Workbook、Worksheet、Range、Cell(註: 連Cell都要,挺麻煩的)... 結束前都建議使用Marshal.FinalReleaseComObject()清掉COM+物件的Reference Counter,確保Excel程式能順利關閉。
修改的範例如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Office.Interop.Excel;
namespace RunExcel
{
class Program
{
static void Main(string[] args)
{
Application excel = null;
_Workbook wrkBook = null;
dynamic sheet = null;
try
{
excel = new Application();
wrkBook = excel.Workbooks.Add();
sheet = wrkBook.Sheets.Add();
sheet.Cells(1, 1).Value = "ABC";
wrkBook.SaveAs(@"d:\PathNotThere\" + Guid.NewGuid() + ".xlsx");
}
catch (Exception ex)
{
Console.WriteLine("Error:" + ex.Message);
}
finally
{
//注意: Excel是Unmanaged程式,要妥善結束才能乾淨不留痕跡
//否則,很容易留下一堆excel.exe在記憶體中
//所有用過的COM+物件都要使用Marshal.FinalReleaseComObject清掉
//COM+物件的Reference Counter,以利結束物件回收記憶體
if (sheet != null)
{
Marshal.FinalReleaseComObject(sheet);
}
if (wrkBook != null)
{
wrkBook.Close(false); //忽略尚未存檔內容,避免跳出提示卡住
Marshal.FinalReleaseComObject(wrkBook);
}
if (excel != null)
{
excel.Workbooks.Close();
excel.Quit();
Marshal.FinalReleaseComObject(excel);
}
}
Console.WriteLine("Done");
}
}
}
由於必須逐一釋放用過的Excel物件,感覺頗麻煩,如果沒有特別需求,我會建議改用OpenXML SDK、EPPlus、ClosedXML等純.NET程式庫[參考1、2]更輕巧單純些。
Comments
# by Ho.Chun
請問 Microsoft.Office.Interop Package 可以直接免費使用嗎 ?
# by Jeffrey
toHo.Chun,它只是串接 Office COM+ 的程式介面,主機要安裝 Office 軟體才能使用。