透過.NET程式操作Excel的注意事項

【個案】某支開啟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程式庫[參考12]更輕巧單純些。

歡迎推文分享:
Published 14 May 2013 07:44 AM 由 Jeffrey
Filed under: ,
Views: 27,324



意見

沒有意見

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<May 2013>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
 
RSS
創用 CC 授權條款
【廣告】
twMVC

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication