close

學過 C#, C++, Java, VB 等語言的人都知道我們可以用  try-catch 區塊來捕捉未預期的例外,但隨著程式規模的增長,我們不可能在所有地方都插入 try-catch 區塊,因此我們必須用一些方式來捕捉整個程式中的例外,比較直覺的方式是用一個 try-catch 把原本的程式碼把整個包起來:

static void Main()
{
    try
    {
        //把所有程式碼寫在這裡
    }
    catch (Exception ex)
    {
        //處裡錯誤並結束程式
    }
}


這個做法有幾個問題:

(1) 如果這是一個視窗程式,System.Windows.Forms.Application 類別有預設的例外處理方式,如果沒有特別指定則例外會被 Application 吃掉而無法被外層的 try-catch 捕捉到。

(2) 這樣只能處理主執行緒的例外,如果是其他執行緒發生的例外則會讓程式直接關閉。

(3) 例外一旦發生,程式很難恢復原始的狀態 (因為已經跳到最外層了)。

下面介紹兩種由 .NET Framework 提供的處理方式。

處理視窗程式中未預期的例外

前面提過 System.Windows.Forms.Application 類別有預設的未預期例外處理方式,只要程式進入訊息迴圈 (呼叫 Application.Run() 或 Form.ShowDialog()) 後未預期的例外處理就會由 Application 類別接管,不過這個處理方式是可以被調整的,主要就是透過 Application.SetUnhandledExceptionMode 方法,它需要一個列舉常數當作參數:

UnhandledExceptionMode.Automatic
除非應用程式的組態檔中另有指定,否則會先檢查 Application.ThreadException 事件有沒有處理的方法,有則引發 ThreadException 事件,沒有則由 Application 預設的方式處理,預設值應該是這個。

UnhandledExceptionMode.ThrowException
不要引發 Application.ThreadException 事件也不要由 Application 自行處理,直接將例外再往外擲出。

UnhandledExceptionMode.CatchException
忽略應用程式的組態檔,先檢查 Application.ThreadException 事件有沒有處理的方法,有則引發 ThreadException 事件,沒有則由 Application 預設的方式處理。

上面有提到 Application.ThreadException 事件,這個事件就是讓程式可以處理在訊息迴圈中沒有捕捉到的例外,可以的話還可以復原狀態讓程式繼續運行。Application.ThreadException 事件會傳入 ThreadExceptionEventArgs 型別的事件資料,透過其 Exception 屬性可以取得擲出的例外物件。

若是讓 Application 自行處理例外,則會顯示一個對話方塊:

預設例外處理

基本上這個對話方塊不是很好看,所以可以的話還是自行處理 Application.ThreadException 事件,下面是一個範例:

using System.Threading;

......

[STAThread]
static void Main()
{
    try
    {
        //初始化
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.SetUnhandledExceptionMode
            (UnhandledExceptionMode.CatchException);
        Application.ThreadException += 
            new ThreadExceptionEventHandler(OnThreadException);

        //啟動主視窗
        Application.Run(new Form1());
    }
    catch (Exception ex)
    {
        //處理其他錯誤並結束程式
    }
}

static void OnThreadException(
    object sender, 
    ThreadExceptionEventArgs e)
{
    //處理錯誤並嘗試恢復狀態
}


處理整個程式中未預期的例外

程式在執行期間會被放在一個 Application Domain (應用程式定義域,類似 Process),而 System.AppDomain 則是表示一個 Application Domain 的類別。AppDomain 類別中有一個 UnhandledException 事件,當程式中任何一個執行緒有未被捕捉的例外被擲出後就會引發此事件,讓程式可以做一些收尾,然後 CLR 再將程式關閉。

AppDomain.UnhandledException 事件會傳入一個 UnhandledExceptionEventArgs 型別的資料,它有兩個屬性:

public object ExceptionObject { get; }
取得未處理的例外物件,因為 CLR 支援不是繼承自 Exception 的例外物件擲出,所以此屬性傳回的是 object。

public bool IsTerminating { get; }
取得 CLR 是否正在中止 (表示事件結束後程式會關閉),.NET 2.0 之後此屬性值只會是 true。

下面是一個範例:

[STAThread]
static void Main()
{
    AppDomain.CurrentDomain.UnhandledException += 
        new UnhandledExceptionEventHandler(OnUnhandledException);
    throw new Exception();
}

static void OnUnhandledException(
    object sender, 
    UnhandledExceptionEventArgs e)
{
    Exception ex = e.ExceptionObject as Exception;
    if (ex != null)
    {
        Console.WriteLine("unhandled exception was occurred : "
            + ex.GetType().ToString());
    }
}


執行之後會印出 unhandled exception was occurred : System.Exception,然後出現:

Application has stop working

因為 CLR 會自動將程式結束,所以就會出現作業系統將程式結束的對話方塊,若是不想出現這個對話方塊,目前我知道的方法就是在 CLR 將程式結束前先自我結束 (這樣就輪不到 CLR 了),最簡單是呼叫 Environment.Exit 方法。

arrow
arrow
    全站熱搜

    hamsters 發表在 痞客邦 留言(1) 人氣()