異常處理之ThreadException、unhandledException及多線程異常處理html
一:ThreadException和unhandledException的區別 windows
處理未捕獲的異常是每一個應用程序起碼有的功能,C#在AppDomain提供了UnhandledException 事件來接收未捕獲到的異常的通知。常見的應用以下: api

{
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能夠阻止應用程序終止。具體使用方法以下: app

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。代碼以下: dom

{
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窗體程序中,若是在窗體線程中, ide
將會觸發ThreadException事件。 post
{
throw new Exception( " 非窗體線程異常 " );
});
t.Start();
將會觸發UnhandledException事件,而後整個應用程序會被終止。this
二:多線程異常處理spa
多線程的異常處理,要採用特殊的作法。如下的處理方式會存在問題:

{
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是屬於新起的異常,因此,正確的作法應該是:

{
try
{
throw new Exception( " 多線程異常 " );
}
catch (Exception error)
{
MessageBox.Show( " 工做線程異常: " + error.Message + Environment.NewLine + error.StackTrace);
}
});
t.Start();
也就是說,新起的線程中異常的捕獲,能夠將線程內部代碼所有try起來。原則上來講,每一個線程本身的異常應該在本身的內部處理完畢,不過仍舊有一個辦法,能夠將線程內部的異常傳遞到主線程。
在Windows窗體程序中,能夠使用窗體的BeginInvoke方法來將異常傳遞給主窗體線程:

{
try
{
throw new Exception( " 非窗體線程異常 " );
}
catch (Exception ex)
{
this .BeginInvoke((Action) delegate
{
throw ex;
});
}
});
t.Start();
上文的代碼將最終引起主線程的Application.ThreadException。最終的結果看起來有點像:
在WPF窗體程序中,你能夠採用以下的方法將工做線程的異常傳遞到主線程:

{
try
{
throw new Exception( " 非窗體線程異常 " );
}
catch (Exception ex)
{
this .Dispatcher.Invoke((Action) delegate
{
throw ex;
} );
}
});
t.Start();
WPF窗體程序的處理方式與Windows窗體程序比較,有兩個頗有意思的地方:
第一個是,在Windows窗體中,咱們採用的是BeginInvoke方法。你會發現使用Invoke方法,並不能引起主線程的Application.ThreadException。而在WPF窗體程序中,不管是調度器的Invoke仍是BeginInvoke方法都能將異常傳遞給主線程。
第二個地方就是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處理的。
補充說明:
AppDomain.CurrentDomain.FirstChanceException事件會在First Chance時觸發。保留部分First Chance有助於排查某些複雜的問題。我一般會保存最近十條First Chance異常,程序完全崩潰時輸出到log。