Catturare globalmente le eccezioni in un'applicazione WPF?


242

Stiamo avendo un'applicazione WPF in cui parti di essa possono generare eccezioni in fase di esecuzione. Vorrei catturare globalmente qualsiasi eccezione non gestita e registrarle, ma altrimenti continuare l'esecuzione del programma come se nulla fosse successo (un po 'come VB On Error Resume Next).

È possibile in C #? E se sì, dove dovrei esattamente inserire il codice di gestione delle eccezioni?

Al momento non riesco a vedere nessun singolo punto in cui posso avvolgere un try/ catcharound e che catturerebbe tutte le eccezioni che potrebbero verificarsi. E anche allora avrei lasciato tutto ciò che è stato eseguito a causa della cattura. O sto pensando in direzioni orribilmente sbagliate qui?

ETA: Perché molte persone sotto lo hanno sottolineato: L'applicazione non è per il controllo di centrali nucleari. Se si arresta in modo anomalo non è un grosso problema, ma le eccezioni casuali che sono per lo più correlate all'interfaccia utente sono un fastidio nel contesto in cui verrebbero utilizzate. Ce n'erano (e probabilmente lo sono ancora) alcuni di questi e poiché utilizza un'architettura plug-in e potrebbe essere esteso da altri (anche studenti in quel caso; quindi nessuno sviluppatore esperto in grado di scrivere codice completamente privo di errori).

Per quanto riguarda le eccezioni che vengono rilevate: le registro in un file di registro, inclusa la traccia dello stack completa. Questo era il punto centrale di quell'esercizio. Solo per contrastare quelle persone che stavano prendendo alla lettera la mia analogia con OERN di VB.

So che ignorare ciecamente determinate classi di errori è pericoloso e potrebbe corrompere la mia istanza dell'applicazione. Come detto prima, questo programma non è fondamentale per nessuno. Nessuno nella loro mente giusta avrebbe scommesso su di essa la sopravvivenza della civiltà umana. È semplicemente un piccolo strumento per testare alcuni approcci progettuali. Ingegneria software.

Per l'uso immediato dell'applicazione non ci sono molte cose che possono accadere su un'eccezione:

  • Nessuna gestione delle eccezioni: finestra di dialogo di errore e uscita dell'applicazione. L'esperimento deve essere ripetuto, sebbene probabilmente con un'altra materia. Non sono stati registrati errori, il che è un peccato.
  • Gestione generica delle eccezioni: errore benigno bloccato, nessun danno fatto. Questo dovrebbe essere il caso comune giudicato da tutti gli errori che abbiamo riscontrato durante lo sviluppo. Ignorare questo tipo di errori non dovrebbe avere conseguenze immediate; le strutture di dati di base sono testate abbastanza bene da poter sopravvivere facilmente.
  • Gestione generica delle eccezioni: errore grave intercettato, possibilmente arresto anomalo in un secondo momento. Questo può succedere raramente. Non l'abbiamo mai visto finora. L'errore viene comunque registrato e un arresto potrebbe essere inevitabile. Quindi questo è concettualmente simile al primo caso. Solo che abbiamo una traccia di stack. E nella maggior parte dei casi l'utente non se ne accorgerà nemmeno.

Per quanto riguarda i dati dell'esperimento generati dal programma: un grave errore causerebbe nella peggiore delle ipotesi la registrazione di dati. Lievi modifiche che cambiano leggermente leggermente il risultato dell'esperimento sono abbastanza improbabili. E anche in quel caso, se i risultati sembrano dubbi, l'errore è stato registrato; si può ancora buttare via quel punto dati se si tratta di un valore anomalo totale.

Riassumendo: Sì, mi considero ancora almeno parzialmente sano e non considero una routine globale di gestione delle eccezioni che lasci il programma in esecuzione necessariamente necessariamente malvagio. Come già detto due volte, tale decisione potrebbe essere valida, a seconda dell'applicazione. In questo caso è stata giudicata una decisione valida e non una cazzata totale e totale. Per qualsiasi altra applicazione tale decisione potrebbe apparire diversa. Ma per favore non accusare me o le altre persone che hanno lavorato a quel progetto per far esplodere il mondo solo perché stiamo ignorando gli errori.

Nota a margine: esiste esattamente un utente per quell'applicazione. Non è qualcosa come Windows o Office che viene utilizzato da milioni di persone in cui il costo di avere eccezioni a tutti gli utenti sarebbe già molto diverso.


Questo non sta davvero pensando: sì, probabilmente vuoi che l'applicazione esca. MA, non sarebbe bello registrare prima l'eccezione con StackTrace? Se tutto ciò che hai ricevuto dall'utente fosse "L'applicazione si è arrestata in modo anomalo quando ho premuto questo pulsante", potresti non essere mai in grado di risolvere il problema perché non avresti informazioni sufficienti. Ma se prima hai registrato l'eccezione prima di interrompere più piacevolmente l'applicazione, avrai molte più informazioni.
Russ,

Ho elaborato un po 'questo punto nella domanda ora. Conosco i rischi connessi e per quella particolare applicazione è stato ritenuto accettabile. E interrompere l'applicazione per qualcosa di semplice come un indice fuori limite mentre l'interfaccia utente ha cercato di fare una bella animazione è eccessivo e non necessario. Sì, non conosco la causa esatta, ma disponiamo di dati per sostenere l'affermazione secondo cui la stragrande maggioranza dei casi di errore è benigna. Quelli seri che stiamo mascherando potrebbero causare il crash dell'applicazione, ma è quello che sarebbe successo senza la gestione globale delle eccezioni.
Joey,

Un'altra nota a margine: se previeni questo incidente, con questo approccio gli utenti lo apprezzeranno molto probabilmente.
lahjaton_j

Vedi Windows Gestione delle eccezioni non gestite nell'esempio WPF (La raccolta più completa di gestori) in C # per Visual Studio 2010 . Ha 5 esempi tra cui AppDomain.CurrentDomain.FirstChanceException, Application.DispatcherUnhandledException e AppDomain.CurrentDomain.UnhandledException.
user34660,

Vorrei aggiungere che il flusso di codice simile a VB On Error Resume Nextnon è possibile in C #. Dopo un Exception(C # non ha "errori") non puoi semplicemente riprendere con la prossima istruzione: l'esecuzione continuerà in un catchblocco - o in uno dei gestori di eventi descritti nelle risposte seguenti.
mike,

Risposte:


191

Usa il Application.DispatcherUnhandledException Event. Vedi questa domanda per un riepilogo (vedi la risposta di Drew Noakes ).

Tenere presente che ci saranno ancora delle eccezioni che impediscono il corretto ripristino dell'applicazione, ad esempio dopo un overflow dello stack, memoria esaurita o perdita della connettività di rete durante il tentativo di salvare nel database.


Grazie. E sì, sono consapevole che ci sono eccezioni da cui non posso recuperare, ma la stragrande maggioranza di quelli che potrebbero accadere sono benevoli in questo caso.
Joey,

14
Se la tua eccezione si verifica su un thread in background (ad esempio, usando ThreadPool.QueueUserWorkItem), questo non funzionerà.
Szymon Rozga,

@siz: buon punto, ma quelli possono essere gestiti con un normale blocco di prova nel workitem, no?
David Schmitt,

1
@PitiOngmongkolkul: il gestore viene chiamato come evento dal tuo ciclo principale. Quando i gestori di eventi tornano, l'app continua normalmente.
David Schmitt,

4
Sembra che dobbiamo impostare e.Handled = true, dove e è un DispatcherUnhandledExceptionEventArgs, per saltare il gestore predefinito che ha chiuso i programmi. msdn.microsoft.com/en-us/library/…
Piti Ongmongkolkul

55

Codice di esempio che utilizza NLog che rileverà le eccezioni generate da tutti i thread in AppDomain , dal thread del dispatcher dell'interfaccia utente e dalle funzioni asincrone :

App.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);
        }
    }

10
Questa è la risposta più completa qui! Sono incluse le eccezioni dello scheduler Taks. Meglio per me, codice pulito e semplice.
Nikola Jovic,

3
Recentemente ho avuto un danneggiamento di App.config presso un cliente e la sua App non si avviava nemmeno perché NLog stava cercando di leggere da App.config e ha lanciato un'eccezione. Poiché quell'eccezione si trovava nell'inizializzatore del logger statico , non veniva rilevata dal UnhandledExceptiongestore. Ho dovuto guardare Visualizzatore registro eventi di Windows per scoprire cosa stava succedendo ...
heltonbiker

Mi raccomando a set e.Handled = true;al UnhandledExceptionche l'applicazione non vada in crash su Eccezioni UI
Apfelkuacha

30

Evento AppDomain.UnhandledException

Questo evento fornisce la notifica di eccezioni non rilevate. Consente all'applicazione di registrare le informazioni sull'eccezione prima che il gestore predefinito del sistema segnali l'eccezione all'utente e termini l'applicazione.

   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);
   }

Se l'evento UnhandledException viene gestito nel dominio dell'applicazione predefinito, viene generato lì per qualsiasi eccezione non gestita in qualsiasi thread, indipendentemente dal dominio dell'applicazione in cui è stato avviato il thread. Se il thread è stato avviato in un dominio dell'applicazione che ha un gestore eventi per UnhandledException, l'evento viene generato in quel dominio dell'applicazione. Se quel dominio dell'applicazione non è il dominio dell'applicazione predefinito e esiste anche un gestore eventi nel dominio dell'applicazione predefinito, l'evento viene generato in entrambi i domini dell'applicazione.

Ad esempio, supponiamo che un thread inizi nel dominio dell'applicazione "AD1", chiama un metodo nel dominio dell'applicazione "AD2" e da lì chiama un metodo nel dominio dell'applicazione "AD3", dove genera un'eccezione. Il primo dominio dell'applicazione in cui è possibile generare l'evento UnhandledException è "AD1". Se quel dominio dell'applicazione non è il dominio dell'applicazione predefinito, l'evento può anche essere generato nel dominio dell'applicazione predefinito.


copiando dall'URL che hai indicato (che parla solo di app console e app WinForms penso): "A partire da .NET Framework 4, questo evento non viene generato per eccezioni che danneggiano lo stato del processo, come overflow dello stack o violazioni dell'accesso, a meno che il gestore eventi non sia critico per la sicurezza e abbia l'attributo HandleProcessCorruptedStateExceptionsAttribute. "
George Birbilis,

1
@GeorgeBirbilis: puoi iscriverti all'evento UnhandledException nel costruttore dell'app.
CharithJ,

18

Inoltre, ciò che altri hanno menzionato qui, nota che combinando il Application.DispatcherUnhandledException(e i suoi simili ) con

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

nel app.configimpedirà il tuo eccezione discussioni secondaria di arrestare l'applicazione.


2

Ecco un esempio completo di utilizzo 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);
        }        
    }


}

-4

Come "VB's On Error Riprendi Next?" Sembra un po 'spaventoso. La prima raccomandazione è non farlo. La seconda raccomandazione è non farlo e non pensarci. Devi isolare meglio i tuoi difetti. Per quanto riguarda come affrontare questo problema, dipende da come è strutturato il codice. Se stai usando un modello come MVC o simili, questo non dovrebbe essere troppo difficile e sicuramente non richiederebbe un swallower di eccezione globale. In secondo luogo, cerca una buona libreria di log come log4net o usa la traccia. Dovremmo avere maggiori dettagli come il tipo di eccezioni di cui stai parlando e quali parti della tua applicazione potrebbero comportare il lancio di eccezioni.


2
Il punto è che voglio mantenere l'applicazione in esecuzione anche dopo eccezioni non rilevate. Voglio solo registrarli (abbiamo un framework di registrazione personalizzato per questo, in base alle nostre esigenze) e non ho bisogno di interrompere l'intero programma solo perché alcuni plugin hanno fatto qualcosa di strano nel suo codice di interazione utente. Da quello che ho visto finora non è banale isolare le cause in modo pulito poiché le diverse parti non si conoscono davvero.
Joey,

9
Capisco, ma devi essere molto cauto nel continuare dopo un'eccezione che non esaminerai, c'è la possibilità di corruzione dei dati e così via da considerare.
BobbyShaftoe,

3
Sono d'accordo con BobbyShaftoe. Questo non è il modo corretto di procedere. Lascia che l'applicazione si blocchi. Se l'errore è su alcuni plugin, quindi correggi o chiedi a qualcuno di riparare il plugin. Lasciarlo funzionare è ESTREMAMENTE pericoloso. Otterrai strani effetti collaterali e raggiungerai un punto in cui non sarai in grado di trovare una spiegazione logica per i bug che si verificano nella tua applicazione.

1
Ho elaborato un po 'questo punto nella domanda ora. Conosco i rischi connessi e per quella particolare applicazione è stato ritenuto accettabile. E interrompere l'applicazione per qualcosa di semplice come un indice fuori limite mentre l'interfaccia utente ha cercato di fare una bella animazione è eccessivo e non necessario. Sì, non conosco la causa esatta, ma disponiamo di dati per sostenere l'affermazione secondo cui la stragrande maggioranza dei casi di errore è benigna. Quelli seri che stiamo mascherando potrebbero causare il crash dell'applicazione, ma è quello che sarebbe successo senza la gestione globale delle eccezioni.
Joey,

Questo dovrebbe essere un commento, non una risposta. Rispondere a "come faccio" con "probabilmente non dovresti" non è una risposta, è un commento.
Josh Noe,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.