簡體   English   中英

在 WPF 應用程序中全局捕獲異常?

[英]Globally catch exceptions in a WPF application?

我們有一個 WPF 應用程序,它的一部分可能會在運行時拋出異常。 我想全局捕獲任何未處理的異常並記錄它們,否則繼續執行程序,就好像什么都沒發生一樣(有點像 VB 的On Error Resume Next )。

這在 C# 中可能嗎? 如果是這樣,我究竟需要將異常處理代碼放在哪里?

目前,我看不到任何可以將try / catch包裹起來的點,並且可以捕獲所有可能發生的異常。 即便如此,我也會因為抓到而離開已經執行的任何東西。 或者我在這里思考的方向非常錯誤?

ETA:因為下面很多人指出:該應用程序不是用於控制核電站。 如果它崩潰了,那沒什么大不了的,但主要與 UI 相關的隨機異常在使用它的上下文中會令人討厭。 有(並且可能仍然是)其中一些,因為它使用插件架構並且可以由其他人擴展(在這種情況下也是學生;所以沒有經驗的開發人員能夠編寫完全無錯誤的代碼)。

至於被捕獲的異常:我確實將它們記錄到日志文件中,包括完整的堆棧跟蹤。 這就是該練習的全部意義所在。 只是為了反駁那些將我的類比與 VB 的 OERN 過於字面的人。

我知道盲目忽略某些類別的錯誤是危險的,並且可能會損壞我的應用程序實例。 如前所述,該程序對任何人來說都不是關鍵任務。 沒有人會以人類文明的生存為賭注。 它只是一個用於測試某些設計方法的小工具。 軟件工程。

對於應用程序的直接使用,異常不會發生很多事情:

  • 無異常處理 - 錯誤對話框和應用程序退出。 必須重復實驗,但可能是針對另一個對象。 沒有記錄任何錯誤,這是不幸的。
  • 通用異常處理 - 良性錯誤被捕獲,無害。 這應該是從我們在開發過程中看到的所有錯誤來判斷的常見情況。 忽略此類錯誤應該不會立即產生后果; 核心數據結構經過充分測試,可以輕松應對。
  • 通用異常處理 - 捕獲嚴重錯誤,稍后可能會崩潰。 這可能很少發生。 到目前為止,我們從未見過它。 無論如何都會記錄錯誤,並且崩潰可能是不可避免的。 所以這在概念上類似於第一種情況。 除了我們有一個堆棧跟蹤。 在大多數情況下,用戶甚至不會注意到。

至於程序生成的實驗數據:一個嚴重的錯誤,最壞的情況是不會記錄任何數據。 微小的改變幾乎不可能改變實驗結果。 即使在這種情況下,如果結果看起來可疑,就會記錄錯誤; 如果它是一個完全異常值,仍然可以丟棄該數據點。

總結一下:是的,我認為自己至少還是部分理智的,我不認為全局異常處理例程會使程序運行必然是完全邪惡的。 如前所述,這種決定可能是有效的,具體取決於應用程序。 在這種情況下,它被認為是一個有效的決定,而不是徹頭徹尾的胡說八道。 對於任何其他應用程序,該決定可能看起來不同。 但是請不要僅僅因為我們忽略了錯誤就指責我或參與該項目的其他人可能會炸毀世界。

旁注:該應用程序只有一個用戶。 它不像 Windows 或 Office 那樣被數百萬人使用,在這些東西中,讓異常冒泡給用戶的成本一開始就已經大不相同了。

使用Application.DispatcherUnhandledException Event 請參閱此問題的摘要(請參閱Drew Noakes 的回答)。

請注意,仍有一些異常會阻止您的應用程序成功恢復,例如在您嘗試保存到數據庫時出現堆棧溢出、內存耗盡或網絡連接丟失等情況。

使用NLog 的示例代碼將捕獲從AppDomain 中的所有線程、 UI 調度程序線程異步函數拋出的異常:

應用程序.xaml.cs :

public partial class App : Application
{
    private static Logger _logger = LogManager.GetCurrentClassLogger();

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        SetupExceptionHandling();
    }

    private void SetupExceptionHandling()
    {
        AppDomain.CurrentDomain.UnhandledException += (s, e) =>
            LogUnhandledException((Exception)e.ExceptionObject, "AppDomain.CurrentDomain.UnhandledException");

        DispatcherUnhandledException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "Application.Current.DispatcherUnhandledException");
            e.Handled = true;
        };

        TaskScheduler.UnobservedTaskException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "TaskScheduler.UnobservedTaskException");
            e.SetObserved();
        };
    }

    private void LogUnhandledException(Exception exception, string source)
    {
        string message = $"Unhandled exception ({source})";
        try
        {
            System.Reflection.AssemblyName assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName();
            message = string.Format("Unhandled exception in {0} v{1}", assemblyName.Name, assemblyName.Version);
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Exception in LogUnhandledException");
        }
        finally
        {
            _logger.Error(exception, message);
        }
    }

AppDomain.UnhandledException事件

此事件提供未捕獲異常的通知。 它允許應用程序在系統默認處理程序向用戶報告異常並終止應用程序之前記錄有關異常的信息。

   public App()
   {
      AppDomain currentDomain = AppDomain.CurrentDomain;
      currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);    
   }

   static void MyHandler(object sender, UnhandledExceptionEventArgs args) 
   {
      Exception e = (Exception) args.ExceptionObject;
      Console.WriteLine("MyHandler caught : " + e.Message);
      Console.WriteLine("Runtime terminating: {0}", args.IsTerminating);
   }

如果 UnhandledException 事件在默認應用程序域中處理,則任何線程中的任何未處理異常都會在那里引發,無論線程在哪個應用程序域中啟動。 如果線程在具有 UnhandledException 事件處理程序的應用程序域中啟動,該事件是在該應用程序域中引發的。 如果該應用程序域不是默認應用程序域,並且默認應用程序域中還有一個事件處理程序,則在兩個應用程序域中都會引發該事件。

例如,假設一個線程在應用程序域“AD1”中啟動,調用應用程序域“AD2”中的方法,並從那里調用應用程序域“AD3”中的方法,並在那里拋出異常。 可以在其中引發 UnhandledException 事件的第一個應用程序域是“AD1”。 如果該應用程序域不是默認應用程序域,則也可以在默認應用程序域中引發該事件。

除了這里其他人提到的,請注意將Application.DispatcherUnhandledException (及其類似的)與

<configuration>
  <runtime>  
    <legacyUnhandledExceptionPolicy enabled="1" />
  </runtime>
</configuration>

app.config中將阻止您的輔助線程異常關閉應用程序。

這是使用NLog完整示例

using NLog;
using System;
using System.Windows;

namespace MyApp
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public App()
        {
            var currentDomain = AppDomain.CurrentDomain;
            currentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            var ex = (Exception)e.ExceptionObject;
            logger.Error("UnhandledException caught : " + ex.Message);
            logger.Error("UnhandledException StackTrace : " + ex.StackTrace);
            logger.Fatal("Runtime terminating: {0}", e.IsTerminating);
        }        
    }


}

就像“VB 出錯時繼續下一步?” 這聽起來有點可怕。 第一個建議是不要這樣做。 第二個建議是不要這樣做,也不要考慮它。 你需要更好地隔離你的錯誤。 至於如何解決這個問題,這取決於你的代碼是如何構建的。 如果您使用的是 MVC 之類的模式,那么這應該不會太困難,並且絕對不需要全局異常吞食器。 其次,尋找一個好的日志庫,如 log4net 或使用跟蹤。 我們需要了解更多細節,例如您所談論的異常類型以及應用程序的哪些部分可能會導致拋出異常。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM