Capturar globalmente exceções em um aplicativo WPF?

242

Estamos tendo um aplicativo WPF em que partes dele podem gerar exceções em tempo de execução. Eu gostaria de capturar globalmente qualquer exceção não tratada e registrá-las, mas, caso contrário, continue a execução do programa como se nada tivesse acontecido (como os VBs On Error Resume Next).

Isso é possível em c #? E se sim, onde exatamente eu precisaria colocar o código de manipulação de exceções?

Atualmente, não consigo ver nenhum ponto em que eu possa quebrar um try/ catchao redor e que capte todas as exceções que possam ocorrer. E mesmo assim eu teria deixado o que foi executado por causa da captura. Ou estou pensando em direções terrivelmente erradas aqui?

ETA: Como muitas pessoas abaixo indicaram: O aplicativo não é para controlar usinas nucleares. Se travar, não é grande coisa, mas as exceções aleatórias relacionadas principalmente à interface do usuário são um incômodo no contexto em que seria usado. Havia (e provavelmente ainda existem) alguns deles, e como ele usa uma arquitetura de plug-in e pode ser estendido por outros (também estudantes nesse caso; portanto, não há desenvolvedores experientes capazes de escrever código completamente livre de erros).

Quanto às exceções detectadas: eu as registro em um arquivo de log, incluindo o rastreamento completo da pilha. Esse era o objetivo desse exercício. Apenas para combater as pessoas que estavam tomando minha analogia com a OERN da VB muito literalmente.

Eu sei que ignorar cegamente certas classes de erros é perigoso e pode corromper minha instância do aplicativo. Como dito anteriormente, este programa não é essencial para ninguém. Ninguém em sã consciência apostaria nela a sobrevivência da civilização humana. É simplesmente uma pequena ferramenta para testar determinadas abordagens de design. Engenharia de software.

Para o uso imediato do aplicativo, não há muitas coisas que podem acontecer em uma exceção:

  • Sem tratamento de exceções - diálogo de erro e saída do aplicativo. A experiência deve ser repetida, embora provavelmente com outro assunto. Nenhum erro foi registrado, o que é lamentável.
  • Tratamento genérico de exceção - erro benigno interceptado, nenhum dano causado. Este deve ser o caso comum julgado por todos os erros que estávamos vendo durante o desenvolvimento. Ignorar esse tipo de erro não deve ter consequências imediatas; as estruturas de dados principais são testadas o suficiente para sobreviverem facilmente a isso.
  • Tratamento de exceção genérico - erro grave interceptado, possivelmente travar em um momento posterior. Isso pode acontecer raramente. Nós nunca vimos isso até agora. O erro é registrado de qualquer maneira e uma falha pode ser inevitável. Portanto, isso é conceitualmente semelhante ao primeiro caso. Exceto que temos um rastreamento de pilha. E na maioria dos casos, o usuário nem percebe.

Quanto aos dados do experimento gerados pelo programa: um erro grave na pior das hipóteses faria com que nenhum dado fosse registrado. Mudanças sutis que alteram um pouco o resultado do experimento são bastante improváveis. E mesmo nesse caso, se os resultados parecerem duvidosos, o erro foi registrado; ainda é possível jogar fora esse ponto de dados, se for um total discrepante.

Para resumir: Sim, eu me considero ainda pelo menos parcialmente sã e não considero uma rotina global de manipulação de exceções que deixa o programa em execução necessariamente necessariamente totalmente mau. Como dito duas vezes antes, essa decisão pode ser válida, dependendo da aplicação. Nesse caso, foi julgada uma decisão válida e não besteira total e absoluta. Para qualquer outro aplicativo, essa decisão pode parecer diferente. Mas, por favor, não me acuse ou a outras pessoas que trabalharam nesse projeto para potencialmente explodir o mundo só porque estamos ignorando erros.

Nota lateral: existe exatamente um usuário para esse aplicativo. Não é algo como o Windows ou o Office que é usado por milhões, onde o custo de ter exceções borbulhar para o usuário já seria muito diferente em primeiro lugar.

Joey
fonte
Isso realmente não está pensando - sim, você provavelmente deseja que o aplicativo saia. MAS, não seria bom primeiro registrar a exceção com seu StackTrace? Se tudo que você obteve do usuário foi "Seu aplicativo travou quando pressionei este botão", talvez você nunca consiga resolver o problema porque não possui informações suficientes. Mas se você registrou a exceção pela primeira vez antes de abortar o aplicativo de maneira mais agradável, terá significativamente mais informações.
Russ
Eu elaborei esse ponto um pouco na questão agora. Conheço os riscos envolvidos e, para esse aplicativo em particular, foi considerado aceitável. E abortar o aplicativo por algo tão simples quanto um índice fora dos limites, enquanto a interface do usuário tentou fazer algumas animações agradáveis é um exagero e desnecessário. Sim, não sei a causa exata, mas temos dados para fazer backup da alegação de que a grande maioria dos casos de erro é benigna. Os graves que estamos mascarando podem causar falha no aplicativo, mas é o que teria acontecido sem o tratamento de exceções globais de qualquer maneira.
Joey
Outra observação: se você evitar falhas com essa abordagem, é provável que os usuários a amem.
Lahjaton_j 25/05
Consulte o exemplo de manipulação de exceções não tratadas do Windows no WPF (a coleção mais completa de manipuladores) em C # para o Visual Studio 2010 . Possui 5 exemplos, incluindo AppDomain.CurrentDomain.FirstChanceException, Application.DispatcherUnhandledException e AppDomain.CurrentDomain.UnhandledException.
precisa saber é o seguinte
Gostaria de acrescentar que o fluxo de código semelhante ao VB On Error Resume Nextnão é possível em C #. Depois de um Exception(C # não tem "erros"), você não pode simplesmente continuar com a próxima instrução: a execução continuará em um catchbloco - ou em um dos manipuladores de eventos descritos nas respostas abaixo.
mike

Respostas:

191

Use o Application.DispatcherUnhandledException Event . Veja esta pergunta para um resumo (consulte a resposta de Drew Noakes ).

Esteja ciente de que ainda haverá exceções que impedem a retomada bem-sucedida do seu aplicativo, como após um estouro de pilha, memória esgotada ou perda de conectividade de rede enquanto você tenta salvar no banco de dados.

David Schmitt
fonte
Obrigado. E sim, estou ciente de que há exceções das quais não posso me recuperar, mas a grande maioria daquelas que poderiam acontecer são benignas nesse caso.
314 Joey
14
Se sua exceção ocorrer em um thread em segundo plano (por exemplo, usando ThreadPool.QueueUserWorkItem), isso não funcionará.
Szymon Rozga
@ siz: bom ponto, mas esses podem ser manipulados com um bloco de tentativa normal no item de trabalho, não?
21410 David Schmitt
1
@PitiOngmongkolkul: O manipulador é chamado como evento do loop principal. Quando os manipuladores de eventos retornam, seu aplicativo continua normalmente.
David Schmitt
4
Parece que precisamos definir e.Handled = true, onde e é um DispatcherUnhandledExceptionEventArgs, para ignorar o manipulador padrão que encerra os programas. msdn.microsoft.com/en-us/library/…
Piti Ongmongkolkul
55

Código de exemplo usando o NLog que capturará exceções lançadas de todos os threads no AppDomain , do thread do dispatcher interface usuário e das funções assíncronas :

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);
        }
    }
Hüseyin Yağlı
fonte
10
Esta é a resposta mais completa aqui! As exceções do planejador Taks estão incluídas. Melhor para mim, código limpo e simples.
Nikola Jovic 21/02
3
Recentemente, tive uma corrupção do App.config em um cliente, e o aplicativo dela nem estava sendo iniciado porque o NLog estava tentando ler o App.config e lançou uma exceção. Como essa exceção estava no inicializador estático do criador de logs , ela não estava sendo capturada pelo UnhandledExceptionmanipulador. Eu tinha de olhar para o Windows Event Log Viewer para encontrar o que estava acontecendo ...
heltonbiker
Eu recomendo para conjunto e.Handled = true;no UnhandledExceptionque a aplicação não irá falhar em Exceções UI
Apfelkuacha
30

Evento AppDomain.UnhandledException

Este evento fornece notificação de exceções não capturadas. Ele permite que o aplicativo registre informações sobre a exceção antes que o manipulador padrão do sistema relate a exceção ao usuário e encerre o aplicativo.

   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 o evento UnhandledException for tratado no domínio do aplicativo padrão, ele será gerado para qualquer exceção não tratada em qualquer encadeamento, independentemente do domínio do aplicativo em que o encadeamento foi iniciado. Se o encadeamento iniciado em um domínio de aplicativo que possui um manipulador de eventos para UnhandledException, o evento é gerado nesse domínio de aplicativo. Se esse domínio de aplicativo não for o domínio de aplicativo padrão e também houver um manipulador de eventos no domínio de aplicativo padrão, o evento será gerado nos dois domínios de aplicativo.

Por exemplo, suponha que um encadeamento inicie no domínio do aplicativo "AD1", chame um método no domínio do aplicativo "AD2" e, a partir daí, chame um método no domínio do aplicativo "AD3", onde ele lança uma exceção. O primeiro domínio do aplicativo no qual o evento UnhandledException pode ser gerado é "AD1". Se esse domínio de aplicativo não for o domínio de aplicativo padrão, o evento também poderá ser gerado no domínio de aplicativo padrão.

CharithJ
fonte
copiando da URL que você apontou (que fala apenas de aplicativos de console e aplicativos WinForms): "A partir do .NET Framework 4, esse evento não é gerado para exceções que corrompem o estado do processo, como estouros de pilha ou violações de acesso, a menos que o manipulador de eventos seja crítico à segurança e possua o atributo HandleProcessCorruptedStateExceptionsAttribute. "
precisa
1
@ GeorgeBirbilis: Você pode se inscrever no evento UnhandledException no construtor App.
CharithJ
18

Além do que outros mencionaram aqui, observe que a combinação de Application.DispatcherUnhandledException(e seus similares ) com

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

no app.configimpedirá que a exceção de encadeamentos secundários encerre o aplicativo.

Hertzel Guinness
fonte
2

Aqui está um exemplo completo usando 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);
        }        
    }


}
Desenvolvedor
fonte
-4

Como "VB's On Error Resume Next?" Isso parece meio assustador. A primeira recomendação é não fazê-lo. A segunda recomendação é não fazê-lo e não pensar nisso. Você precisa isolar melhor suas falhas. Quanto à forma de abordar esse problema, isso depende de como o código está estruturado. Se você estiver usando um padrão como MVC ou similar, isso não deve ser muito difícil e definitivamente não exigiria um engolidor de exceção global. Em segundo lugar, procure uma boa biblioteca de log como log4net ou use rastreamento. Precisamos saber mais detalhes, como quais tipos de exceções você está falando e quais partes do seu aplicativo podem resultar no lançamento de exceções.

BobbyShaftoe
fonte
2
O ponto é que eu quero manter o aplicativo em execução, mesmo após exceções não capturadas. Eu só quero registrá-los (temos uma estrutura de log customizada para isso, com base em nossas necessidades) e não preciso interromper o programa inteiro apenas porque algum plug-in fez algo estranho no código de interação do usuário. Pelo que vi até agora, não é trivial isolar as causas de maneira limpa, pois as diferentes partes não se conhecem realmente.
Joey
9
Entendo, mas você deve ter muito cuidado em continuar após uma exceção que não examina, existe a possibilidade de corrupção de dados e assim por diante.
BobbyShaftoe
3
Eu concordo com o BobbyShaftoe. Este não é o caminho correto a seguir. Deixe o aplicativo falhar. Se a falha estiver em algum plug-in, corrija ou peça a alguém que o conserte. Deixá-lo em execução é EXTREMAMENTE perigoso. Você terá efeitos colaterais estranhos e chegará a um ponto em que não conseguirá encontrar uma explicação lógica para os bugs que estão acontecendo no seu aplicativo.
1
Eu elaborei esse ponto um pouco na questão agora. Conheço os riscos envolvidos e, para esse aplicativo em particular, foi considerado aceitável. E abortar o aplicativo por algo tão simples quanto um índice fora dos limites, enquanto a interface do usuário tentou fazer algumas animações agradáveis ​​é um exagero e desnecessário. Sim, não sei a causa exata, mas temos dados para fazer backup da alegação de que a grande maioria dos casos de erro é benigna. Os graves que estamos mascarando podem causar falha no aplicativo, mas é o que teria acontecido sem o tratamento de exceções globais de qualquer maneira.
Joey
Isso deve ser um comentário, não uma resposta. Responder a "como faço isso" com "você provavelmente não deveria" não é uma resposta, é um comentário.
Josh Noe