Глобально ловить исключения в приложении WPF?

242

У нас есть приложение WPF, где его части могут генерировать исключения во время выполнения. Я хотел бы глобально перехватить любое необработанное исключение и записать их в журнал, но в противном случае продолжить выполнение программы, как будто ничего не произошло (вроде как в VB On Error Resume Next).

Возможно ли это в C #? И если да, то где именно мне нужно поместить код обработки исключений?

В настоящее время я не вижу какой-либо одной точки, где я мог бы обернуть try/ catchвокруг, и которая поймала бы все исключения, которые могли произойти. И даже тогда я бы оставил все, что было выполнено из-за улова. Или я здесь думаю в совершенно неправильном направлении?

ETA: Потому что многие люди ниже указали на это: приложение не для управления атомными электростанциями. Если он выходит из строя, это не так уж важно, но случайные исключения, которые в основном связаны с пользовательским интерфейсом, являются неприятностью в контексте, где он будет использоваться. Их было (и, вероятно, все еще осталось) несколько, и, поскольку они используют архитектуру плагинов и могут быть расширены другими (в данном случае также учащимися; так что нет опытных разработчиков, способных написать абсолютно безошибочный код).

Что касается обнаруженных исключений: я записываю их в файл журнала, включая полную трассировку стека. В этом весь смысл этого упражнения. Просто чтобы противостоять тем людям, которые слишком буквально воспринимали мою аналогию с OERN VB.

Я знаю, что слепое игнорирование определенных классов ошибок опасно и может повредить экземпляр моего приложения. Как уже было сказано, эта программа ни для кого не важна. Никто в здравом уме не поставил бы на это выживание человеческой цивилизации. Это просто маленький инструмент для тестирования определенных подходов к дизайну. разработка программного обеспечения.

Для немедленного использования приложения не так много вещей, которые могут произойти с исключением:

  • Нет обработки исключений - диалог ошибок и выход из приложения. Эксперимент должен быть повторен, хотя, вероятно, с другим предметом. Ошибки не были зарегистрированы, что вызывает сожаление.
  • Обработка исключений общего характера - доброкачественная ошибка в ловушке, никакого вреда не причинено. Это должно быть обычным делом, судя по всем ошибкам, которые мы видели во время разработки. Игнорирование ошибок такого рода не должно иметь немедленных последствий; основные структуры данных проверены достаточно хорошо, чтобы они могли легко пережить это.
  • Обработка исключений общего характера - серьезная ошибка в ловушке, возможно сбой на более позднем этапе. Такое может случаться редко. Мы никогда не видели это до сих пор. В любом случае ошибка регистрируется, и сбой может быть неизбежным. Так что это концептуально похоже на самый первый случай. За исключением того, что у нас есть трассировка стека. И в большинстве случаев пользователь даже не заметит.

Что касается экспериментальных данных, сгенерированных программой: серьезная ошибка в худшем случае просто не приведет к записи данных. Незначительные изменения, которые слегка изменяют результат эксперимента, маловероятны. И даже в том случае, если результаты кажутся сомнительными, ошибка регистрировалась; все еще можно отбросить эту точку данных, если она является полным выбросом.

Подводя итог: Да, я считаю себя все еще, по крайней мере, частично вменяемым, и я не считаю, что глобальная подпрограмма обработки исключений, которая оставляет работающую программу, обязательно является абсолютно злой. Как уже было сказано дважды, такое решение может быть действительным, в зависимости от приложения. В данном случае было вынесено верное решение, а не полная и полная чушь. Для любого другого приложения это решение может выглядеть иначе. Но, пожалуйста, не обвиняйте меня или других людей, которые работали над этим проектом, чтобы потенциально взорвать мир только потому, что мы игнорируем ошибки.

Примечание: для этого приложения есть только один пользователь. Это не что-то вроде Windows или Office, которым пользуются миллионы, когда стоимость получения пузырей исключений для пользователя будет совсем другой, во-первых.

детеныш
источник
Это на самом деле не задумываясь - да, вы, вероятно, хотите, чтобы приложение завершилось. НО, не было бы неплохо сначала зарегистрировать исключение с помощью StackTrace? Если все, что вы получили от пользователя, было: «Сбой приложения, когда я нажал эту кнопку», вы, возможно, никогда не сможете решить проблему, потому что у вас не будет достаточной информации. Но если вы сначала зарегистрировали исключение, прежде чем более приятно прервать приложение, у вас будет значительно больше информации.
Расс
Сейчас я немного уточнил этот вопрос. Я знаю риски, и для этого конкретного применения это было сочтено приемлемым. А отмена приложения для чего-то столь же простого, как индекс за пределами границ, в то время как пользовательский интерфейс пытался сделать хорошую анимацию, является излишним и ненужным. Да, я не знаю точной причины, но у нас есть данные, подтверждающие утверждение о том, что подавляющее большинство случаев ошибок является доброкачественным. Серьезные, которые мы маскируем, могут привести к сбою приложения, но в любом случае это произошло бы без обработки глобальных исключений.
Джои
Еще одно замечание: если вы предотвратите любые сбои с помощью этого подхода, пользователям, скорее всего, понравится.
lahjaton_j
Посмотрите пример обработки необработанных исключений Windows в WPF (наиболее полная коллекция обработчиков) в C # для Visual Studio 2010 . Он имеет 5 примеров, включая AppDomain.CurrentDomain.FirstChanceException, Application.DispatcherUnhandledException и AppDomain.CurrentDomain.UnhandledException.
user34660
Я хотел бы добавить, что VB-подобный поток кода On Error Resume Nextв C # невозможен. После Exception(в C # нет «ошибок») вы не можете просто продолжить со следующего оператора: выполнение будет продолжено в catchблоке - или в одном из обработчиков событий, описанных в ответах ниже.
микрофон

Ответы:

191

Используйте Application.DispatcherUnhandledException Event. См. Этот вопрос для краткого изложения (см. Ответ Дрю Ноакс ).

Имейте в виду, что все еще будут исключения, которые препятствуют успешному возобновлению работы вашего приложения, например, после переполнения стека, исчерпания памяти или потери сетевого подключения, пока вы пытаетесь сохранить в базу данных.

Дэвид Шмитт
источник
Спасибо. И да, я знаю, что есть исключения, от которых я не могу оправиться, но подавляющее большинство из тех, которые могут произойти, в этом случае доброкачественные.
Джои
14
Если ваше исключение происходит в фоновом потоке (скажем, с использованием ThreadPool.QueueUserWorkItem), это не будет работать.
Шимон Розга
@siz: хорошая мысль, но с рабочим блоком можно справиться обычным блоком try, не так ли?
Дэвид Шмитт
1
@PitiOngmongkolkul: обработчик вызывается как событие из вашего основного цикла. Когда обработчики событий возвращаются, ваше приложение продолжает работать как обычно.
Дэвид Шмитт
4
Кажется, что нам нужно установить e.Handled = true, где e - DispatcherUnhandledExceptionEventArgs, чтобы пропустить обработчик по умолчанию, который завершает программы. msdn.microsoft.com/en-us/library/…
Пити Онгмонгколкул,
56

Пример кода с использованием NLog, который будет перехватывать исключения, генерируемые из всех потоков в AppDomain , из потока диспетчера пользовательского интерфейса и из асинхронных функций :

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
Это самый полный ответ здесь! Такс планировщик исключения включены. Лучший для меня, чистый и простой код.
Никола Йович
3
Недавно у меня было повреждение App.config у клиента, и ее приложение даже не запустилось, потому что NLog пытался читать из App.config и выдал исключение. Так как это исключение было в инициализаторе статического логгера , оно не было перехвачено UnhandledExceptionобработчиком. Я должен был посмотреть на Windows Event Log Viewer, чтобы найти, что происходит ...
heltonbiker
Я рекомендую установить e.Handled = true;на то, UnhandledExceptionчто приложение не будет аварийно
завершать работу в
30

Событие 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». Если этот домен приложения не является доменом приложения по умолчанию, событие также может быть вызвано в домене приложения по умолчанию.

CharithJ
источник
копирование с URL-адреса, на который вы указали (я думаю, что речь идет о консольных приложениях и приложениях WinForms): «Начиная с .NET Framework 4, это событие не вызывается для исключений, которые повреждают состояние процесса, например переполнения стека или нарушения доступа, если обработчик событий не критичен для безопасности и не имеет атрибута HandleProcessCorruptedStateExceptionsAttribute. "
Джордж Бирбилис
1
@GeorgeBirbilis: Вы можете подписаться на событие UnhandledException в конструкторе приложения.
CharithJ
18

Кроме того, что другие упоминали здесь, обратите внимание, что объединение Application.DispatcherUnhandledException(и его аналог ) с

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

В этом app.configслучае исключение вторичных потоков не сможет завершить работу приложения.

Герцель Гиннесс
источник
2

Вот полный пример использования 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

Как "VB в случае ошибки продолжить дальше?" Это звучит немного страшно. Первая рекомендация - не делай этого. Вторая рекомендация - не делай этого и не думай об этом. Вы должны лучше изолировать свои недостатки. От того, как решить эту проблему, зависит то, как ваш код структурирован. Если вы используете шаблон, такой как MVC или тому подобное, то это не должно быть слишком сложно и определенно не потребует проглотителя глобальных исключений. Во-вторых, найдите хорошую библиотеку журналов, такую ​​как log4net, или используйте трассировку. Нам нужно знать больше деталей, например, о каких исключениях вы говорите и какие части вашего приложения могут привести к созданию исключений.

BobbyShaftoe
источник
2
Дело в том, что я хочу, чтобы приложение работало даже после неперехваченных исключений. Я просто хочу регистрировать их (у нас есть специальная структура регистрации для этого, исходя из наших потребностей), и мне не нужно прерывать всю программу только потому, что какой-то плагин сделал что-то странное в своем коде взаимодействия с пользователем. Из того, что я видел до сих пор, нетрудно четко выделить причины, так как разные части действительно не знают друг друга.
Джои
9
Я понимаю, но вы должны быть очень осторожны, чтобы продолжить после исключения, которое вы не изучаете, есть вероятность повреждения данных и так далее, чтобы рассмотреть.
BobbyShaftoe
3
Я согласен с BobbyShaftoe. Это не правильный путь. Пусть приложение вылетает. Если ошибка в каком-то плагине, то исправьте или попросите кого-нибудь исправить плагин. Оставлять его работающим ЧРЕЗВЫЧАЙНО опасно. Вы получите странные побочные эффекты и достигнете точки, когда вы не сможете найти логическое объяснение ошибок, возникающих в вашем приложении.
1
Сейчас я немного уточнил этот вопрос. Я знаю риски, и для этого конкретного применения это было сочтено приемлемым. А отмена приложения для чего-то столь же простого, как индекс за пределами границ, в то время как пользовательский интерфейс пытался сделать хорошую анимацию, является излишним и ненужным. Да, я не знаю точной причины, но у нас есть данные, подтверждающие утверждение о том, что подавляющее большинство случаев ошибок является доброкачественным. Серьезные, которые мы маскируем, могут привести к сбою приложения, но в любом случае это произошло бы без обработки глобальных исключений.
Джои
Это должен быть комментарий, а не ответ. Отвечать на вопрос «как мне это сделать» словами «вы, вероятно, не должны» - это не ответ, а комментарий.
Джош Ноу