異常處理之ThreadException、unhandledException及多線程異常處理

作者: luminji  來源: 博客園  發布時間: 2011-01-06 11:07  閱讀: 2744 次  推薦: 0   原文鏈接   [收藏]  

  一:ThreadException和unhandledException的區別

  處理未捕獲的異常是每個應用程序起碼有的功能,C#在AppDomain提供了UnhandledException 事件來接收未捕獲到的異常的通知。常見的應用如下:   

代碼
 
static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException
+= new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
}


static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception error
= (Exception)e.ExceptionObject;
Console.WriteLine(
"MyHandler caught : " + error.Message);
}

  未捕獲的異常,通常就是運行時期的BUG,于是我們可以在UnhandledException 的注冊事件方法CurrentDomain_UnhandledException中將未捕獲異常的信息記錄在日志中。值得注意的是,UnhandledException提供的機制并不能阻止應用程序終止,也就是說,CurrentDomain_UnhandledException方法執行后,應用程序就會被終止。

  上面我們舉的例子來自于控制臺程序,UnhandledException可以在任何應用程序域中使用,在某些應用程序模型,如windows窗體程序,還存在ThreadException來處理 Windows 窗體線程中所發生的其未經處理的異常。即,在windows窗體程序中,使用 ThreadException 事件來處理 UI 線程異常,使用 UnhandledException 事件來處理非 UI 線程異常。ThreadException可以阻止應用程序終止。具體使用方法如下:  

代碼
 
[STAThread]
static void Main()
{
Application.ThreadException
+= new ThreadExceptionEventHandler(UIThreadException);
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException
+=
new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
Application.Run(
new ErrorHandlerForm());
}


private static void UIThreadException(object sender, ThreadExceptionEventArgs t)
{

try
{
string errorMsg = "Windows窗體線程異常 : \n\n";
MessageBox.Show(errorMsg
+ t.Exception.Message + Environment.NewLine + t.Exception.StackTrace);
}

catch
{
MessageBox.Show(
"不可恢復的Windows窗體異常,應用程序將退出!");
}
}


private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{

try
{
Exception ex
= (Exception)e.ExceptionObject;
string errorMsg = "非窗體線程異常 : \n\n";
MessageBox.Show(errorMsg
+ ex.Message + Environment.NewLine + ex.StackTrace);
}

catch
{
MessageBox.Show(
"不可恢復的非Windows窗體線程異常,應用程序將退出!");
}
}

   除了Windows窗體程序,再來說一下WPF程序。WPF的UI線程和Windows的UI線程有點不一樣。WPF的UI線程是交給一個叫做調度器的類:Dispatcher。代碼如下:   

代碼
 
public App()
{

this.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(Application_DispatcherUnhandledException);
AppDomain.CurrentDomain.UnhandledException
+= new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
}


void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{

try
{
Exception ex
= e.ExceptionObject as Exception;
string errorMsg = "非WPF窗體線程異常 : \n\n";
MessageBox.Show(errorMsg
+ ex.Message + Environment.NewLine + ex.StackTrace);
}

catch
{
MessageBox.Show(
"不可恢復的WPF窗體線程異常,應用程序將退出!");
}
}


private void Application_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{

try
{
Exception ex
= e.Exception;
string errorMsg = "WPF窗體線程異常 : \n\n";
MessageBox.Show(errorMsg
+ ex.Message + Environment.NewLine + ex.StackTrace);
}

catch
{
MessageBox.Show(
"不可恢復的WPF窗體線程異常,應用程序將退出!");
}
}

  無論是Windows窗體程序還是WPF程序,我們都看到捕獲的異常當中分為"窗體線程異常"和"非窗體線程異常"。如在Windows窗體程序中,如果在窗體線程中,       

 
throw new Exception("窗體線程異常");

  將會觸發ThreadException事件。  

 
Thread t = new Thread((ThreadStart)delegate
{
throw new Exception("非窗體線程異常");
});
t.Start();

 將會觸發UnhandledException事件,然后整個應用程序會被終止。

二:多線程異常處理

   多線程的異常處理,要采用特殊的做法。以下的處理方式會存在問題:   

代碼
 
try
{
Thread t
= new Thread((ThreadStart)delegate
{
throw new Exception("多線程異常");
});
t.Start();
}

catch (Exception error)
{
MessageBox.Show(error.Message
+ Environment.NewLine + error.StackTrace);
}

   應用程序并不會在這里捕獲線程t中的異常,而是會直接退出。從.NET2.0開始,任何線程上未處理的異常,都會導致應用程序的退出(先會觸發AppDomain的UnhandledException)。上面代碼中的try-catch實際上捕獲的還是當前線程的異常,而t是屬于新起的異常,所以,正確的做法應該是:  

代碼
 
Thread t = new Thread((ThreadStart)delegate
{
try
{
throw new Exception("多線程異常");
}

catch (Exception error)
{
MessageBox.Show(
"工作線程異常:" + error.Message + Environment.NewLine + error.StackTrace);
}
});
t.Start();

   也就是說,新起的線程中異常的捕獲,可以將線程內部代碼全部try起來。原則上來說,每個線程自己的異常應該在自己的內部處理完畢,不過仍舊有一個辦法,可以將線程內部的異常傳遞到主線程。在Windows窗體程序中,可以使用窗體的BeginInvoke方法來將異常傳遞給主窗體線程:  

代碼
 
Thread t = new Thread((ThreadStart)delegate
{
try
{
throw new Exception("非窗體線程異常");
}

catch (Exception ex)
{

this.BeginInvoke((EventHandler)delegate
{
throw ex;
});
}
});
t.Start();

  上文的代碼將最終引發主線程的Application.ThreadException。最終的結果看起來有點像:  

  在WPF窗體程序中,你可以采用如下的方法將工作線程的異常傳遞到主線程:  

代碼
 
Thread t = new Thread((ThreadStart)delegate
{
try
{
throw new Exception("非窗體線程異常");
}

catch (Exception ex)
{

this.Dispatcher.Invoke((EventHandler)delegate
{
throw ex;
}, System.Windows.Threading.DispatcherPriority.Normal,
"", null);
}
});
t.Start();

  WPF窗體程序的處理方式與Windows窗體程序比較,有三個很有意思的地方:第一個是,在Windows窗體中,我們采用的是BeginInvoke方法。你會發現使用Invoke方法,并不能引發主線程的Application.ThreadException。而在WPF窗體程序中,無論是調度器的Invoke還是BeginInvoke方法都能將異常傳遞給主線程。

  第二個是,WPF窗體程序中,使用調度器的Invoke或BeginInvoke方式時,必須得加上最后兩個沒有用的參數,即""和null。否則引發的將是Invoke或者BeginInvoke方法的"參數計數不匹配"異常,而不是原始異常。關于這一點,可以在WPF主線程的Application的DispatcherUnhandledException事件處理方法中得到驗證,工作線程的異常將會包裝成為主線程異常的InnerException。

  第三個地方就是InnerException。WPF的工作線程異常將會拋到主線程,變成主線程異常的InnerException,而Windows窗體程序的工作線程異常,將會被吃掉,直接變為null,只是在異常的Message信息中保存工作線程異常的Message。

  三:ASP.NET異常處理

  我們都知道ASP.NET的全局異常處理方法是Global中的Application_Error方法。我曾經查過ASP.NET的Appdomain.CurrentDomain.unhandledException,結果用反射得到的結果,unhandledException所注冊的事件方法根本不是這個方法。聯想到ASP.NET頁面,包括這個全局處理類,都是交給aspnet_isapi.dll處理的,而aspnet_isapi.dll不是一個托管程序集。所以,應該理解為,ASP.NET的未捕獲異常的處理,不同于托管異常(即CLR異常),而是交給aspnet_isapi.dll這個非托管DLL處理的。

0
0
 
標簽:異常處理
 
 

文章列表

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()