.netcore全局異常處理

一、背景

某天,應用程序進程無緣無故退出,也就是我們通常說的崩潰。通常情況下,windows事件會記錄一條消息。但是有時候,我們發現這樣的信息,對於查找問題,還是遠遠不夠的,因爲它說RunTime報錯。這時,我就想能不能自己捕獲全局未處理的異常。之所以有這樣的想法,因爲之前在客戶端程序中寫過。這次我要在.netcore中處理,網上搜了一段代碼,高高興興地貼上去了,覺得上了保險箱。

二、探索

AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
 {
    foreach (var item  in e.Exception.InnerExceptions)
    {
      Logger.Error("未捕獲的Task異常 " + item.InnerException.Message + "     " + item.GetType().Name);
    }
 }
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
   Exception exception = (Exception)e.ExceptionObject;
   Logger.Error("未捕獲的Domain異常 : " + exception.Message + "," + exception.StackTrace);
   Logger.Error("Runtime terminating: {0}", e.IsTerminating);
}

給AppDomain和TaskScheduler註冊了兩個未處理異常的方法,等系統拋出異常時,可以捕獲。沒想到,程序一天之內崩潰了兩次,比之前幾個月崩潰一次,頻率不知高了都少倍。下面是崩潰時的信息:

 

 

 這堆棧信息,得仔細看,才能看出門道,否則,可能會把重要的信息遺漏掉。猛的一看,程序哪裏有未將對象引用到實例了?在業務代碼中苦苦思索,沒有找到。第二天早晨,仔細查看這個錯誤信息,發現這個異常竟然是TaskScheduler註冊的這個方法裏面報出來的。於是我再次修改代碼:

  private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
  {
    if (e.Exception?.InnerException != null)
    {
       foreach (var item in e.Exception.InnerExceptions)
       {
          Logger.Error("未捕獲的Task異常 " + item.InnerException.Message + "     " + item.GetType().Name);
       }
    }
    else
     {
          Logger.Error("[Exception]未捕獲的Task異常 " + e.Exception?.Message + "     " + e.Exception?.StackTrace);
     }
      //將異常標識爲已經觀察到 
      e.SetObserved();
  }

代碼是修改好了,在本地如何調試呢?網上說,GC回收Task的時候,會觸發Task裏的異常,這個說法,應該是正確的,請看上面的堆棧信息,回收的時候,會報異常發佈出去。好,那我就人爲製造一個異常:

Task.Run(() =>
{
   throw new Exception("測試異常");
});
Thread.Sleep(2000);
GC.Collect();

可是代碼跑起來,沒有捕獲到任何異常。我以爲GC沒有運行,我在網上搜索答案,類似這樣的寫法:


Task.Run(() =>
{
   throw new Exception("測試異常");
});
while(true){
  //不停地給數組分配內存
  //調用GC
}

這次代碼運行起來,不僅異常沒有捕獲到,程序直接崩潰,說內存不足,最後筆記本發燙,導致了藍屏。我不得不重啓電腦。

三、處理

網上一篇文章說,在Debug模式下,捕獲不到異常。Release下可以。於是,我切換了模式,果然可以。

 Logger.Error("未捕獲的Task異常 " + item.InnerException.Message + "     " + item.GetType().Name);

在處理全局異常的方法裏,我記錄了日誌,就這一句引發了未將對象引用到實例,調試發現  itm.InnerException爲null,所以調用Message就異常了。下面,我們來處理:

private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
foreach (var item in e.Exception.InnerExceptions){
      Logger.Error("未捕獲的Task異常 " + item.InnerException?.Message + "     " + item.GetType().Name);
   }
}

處理好了,日誌輸出:未捕獲的Task異常    Exception,從調試角度看,這樣的信息,就是個廢話,改改代碼:

private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
    foreach (var item in e.Exception.InnerExceptions)
    {
      Logger.Error("未捕獲的Task異常 " + item.Message + "," + item.StackTrace);
    }
}

調試結果如下:

 

 這樣代碼就好了嗎?我擔心儘管處理好後,進程還會退出,網上搜了下,可以加入這句:

//將異常標識爲已經觀察到 
e.SetObserved();

經過調試,發現少了這句,也不會有問題,這句意思是不讓異常繼續往上冒泡,到此爲止。這樣,程序就好了嗎?還是有所擔心,終極版的代碼:

private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
    try
    {
        foreach (var item in e.Exception.InnerExceptions)
        {
            Logger.Error("未捕獲的Task異常 " + item.Message + "," + item.StackTrace);
        }
    }
    catch (Exception ex)
    {
        Logger.Error($"TaskScheduler_UnobservedTaskException處理異常:{ex.Message}");
    }
    //阻止異常冒泡
    e.SetObserved();
}

這裏之所以加上try..catch,因爲擔心Logger出現異常,進程照樣會崩潰。所以,既想捕獲應用程序中Task中的異常,又不想因此把程序整垮。

四、後記

網上的代碼,僅供參考和學習,要上服務器,還得經過本地嚴格測試,誰知道會什麼時候會引發災難。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章