Глобально ловити винятки у програмі WPF?


242

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

Чи можливо це в C #? І якщо так, то де саме мені потрібно поставити код обробки винятків?

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

ETA: Оскільки багато людей нижче вказали на це: додаток не для управління атомними електростанціями. Якщо вона виходить з ладу, це не так вже й велика справа, але випадкові винятки, що стосуються переважно інтерфейсу користувача, викликають неприємності в контексті, де вона буде використовуватися. Було (і, мабуть, все ще) декілька з них, і оскільки вона використовує архітектуру плагінів і може бути розширена іншими (також студенти в цьому випадку, тому немає досвідчених розробників, які можуть написати повністю код без помилок).

Що стосується винятків, які потрапляють: я реєструю їх у файлі журналу, включаючи повний слід стека. В цьому і полягала вся суть цієї вправи. Просто для протидії тим людям, які надто буквально сприймали мою аналогію до VERN's OERN.

Я знаю, що сліпе ігнорування певних класів помилок небезпечно і може пошкодити екземпляр моєї програми. Як говорилося раніше, ця програма не є критичною для будь-якої місії. Ніхто з їх розумного розуму не став би на це виживання людської цивілізації. Це просто невеликий інструмент для тестування певних підходів до проектування. розробка програмного забезпечення.

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

  • Не виняток - обробка помилок та вихід із програми. Експеримент потрібно повторити, хоча ймовірно з іншою темою. Жодних помилок не було зафіксовано, що шкода.
  • Загальне поводження з винятками - доброякісна помилка в пастці, шкоди не завдано. Це має бути загальний випадок, який судять з усіх помилок, які ми спостерігали під час розробки. Ігнорування подібних помилок не повинно мати негайних наслідків; основні структури даних перевірені досить добре, щоб вони легко пережили це.
  • Загальне поводження з винятками - серйозна помилка в пастці, можливо, аварія в більш пізній момент. Це може траплятися рідко. Ми ніколи цього не бачили. Помилка реєструється в будь-якому випадку, і збій може бути неминучим. Тож це концептуально схоже на перший випадок. За винятком того, що у нас є слід стека. І в більшості випадків користувач навіть не помітить.

Що стосується даних експерименту, згенерованих програмою: Серйозна помилка, в гіршому випадку, просто не призведе до запису даних. Тонкі зміни, які так незначно змінюють результат експерименту, є малоймовірними. І навіть у тому випадку, якщо результати здаються сумнівними, помилка була зафіксована; все-таки можна викинути цю точку даних, якщо це тотальний вигляд.

Підводячи підсумок: Так, я вважаю себе як мінімум частково здоровим, і не вважаю глобальну процедуру поводження з винятками, яка залишає програму обов'язково абсолютно злою. Як було сказано двічі раніше, таке рішення може бути дійсним, залежно від заявки. У цьому випадку його судили правильним рішенням, а не тотальним і повним глупотою. Для будь-якої іншої програми це рішення може виглядати інакше. Але, будь ласка, не звинувачуйте мене чи інших людей, які працювали над цим проектом, щоб потенційно підірвати світ лише тому, що ми ігноруємо помилки.

Побічна примітка: Для цього додатка існує точно один користувач. Це не щось на зразок Windows або Office, яким мільйони звикають, коли вартість наявності міхура винятків для користувача взагалі була б дуже різною в першу чергу.


Це насправді не замислюється - так, напевно, ви хочете, щоб програма закрила. Але НЕ було б непогано спочатку записати виняток своїм StackTrace? Якщо все, що ви отримали від користувача, було "Ваша програма завершилась, коли я натиснув цю кнопку", ви, можливо, ніколи не зможете вирішити проблему, оскільки у вас не буде достатньої інформації. Але якщо ви вперше зареєстрували виняток перед тим, як приємніше перервати додаток, у вас буде значно більше інформації.
Русс

Зараз я трохи детальніше запитав це питання. Я знаю, що пов'язані з ризиками, і для цієї конкретної програми він вважався прийнятним. І переривання програми для чогось такого простого, як індекс поза межами, в той час як користувальницький інтерфейс намагався зробити приємну анімацію, це зайве і непотрібне. Так, я не знаю точної причини, але у нас є дані для підтвердження твердження, що переважна більшість випадків помилок є доброякісними. Серйозні, які ми маскуємо, можуть призвести до збоїв у програмі, але це все-таки сталося б без обліку глобальних винятків.
Joey

Ще одна сторона: якщо ви запобіжете будь-які збої при такому підході, користувачі, швидше за все, люблять це.
lahjaton_j

Див. Розділ Обробка Windows без оброблених винятків у зразку WPF (Найповніша колекція обробників) в C # для Visual Studio 2010 . У ньому є 5 прикладів, включаючи AppDomain.CurrentDomain.FirstChanceException, Application.DispatcherUnhandledException та AppDomain.CurrentDomain.UnhandledException.
користувач34660

Я хотів би додати, що VB-подібний потік коду On Error Resume Nextв C # неможливий. Після того, як у Exception(C # немає "помилок"), ви не можете просто відновити наступне твердження: виконання буде продовжено в catchблоці - або в одному з обробників подій, описаних у відповідях нижче.
Майк

Відповіді:


191

Використовуйте Application.DispatcherUnhandledException Event. Дивіться це запитання для підсумкового запису (див. Відповідь Дрю Ноакса ).

Пам’ятайте, що все ще будуть винятки, які перешкоджають успішному відновленню вашої програми, як-от після переповнення стека, вичерпаної пам’яті чи втраченого мережевого підключення, коли ви намагаєтесь зберегти в базі даних.


Дякую. І так, я знаю, що є винятки, після яких я не можу оговтатися, але переважна більшість тих, що можуть статися, є доброякісними в даному випадку.
Joey

14
Якщо ваш виняток трапляється на фоновому потоці (скажімо, за допомогою ThreadPool.QueueUserWorkItem), це не працюватиме.
Szymon Rozga

@siz: хороший момент, але з ними можна обробляти звичайну спробу блоку в робочому процесі, ні?
Девід Шмітт

1
@PitiOngmongkolkul: Обробник викликається як подія з вашого основного циклу. Коли обробники подій повернуться, ваш додаток продовжується нормально.
Девід Шмітт

4
Здається, нам потрібно встановити e.Handled = true, де e DispatcherUnhandledExceptionEventArgs, щоб пропустити обробник за замовчуванням, який вийшов із програм. msdn.microsoft.com/en-us/library/…
Piti Ongmongkolkul

55

Приклад коду за допомогою 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
Тут найповніша відповідь! Виключення з планувальника Taks включені. Найкращий для мене, чистий і простий код.
Микола Йович

3
Нещодавно у клієнта сталася пошкодження App.config, і її додаток навіть не запускався, оскільки NLog намагався читати з App.config і кинув виняток. Оскільки цей виняток був у ініціалізаторі статичного реєстратора , UnhandledExceptionобробник не потрапив у нього . Мені довелося подивитися на переглядач журналів подій Windows, щоб знайти те, що відбувається ...
heltonbiker

Я рекомендую , щоб встановити e.Handled = true;на UnhandledExceptionтому , що програма не буде врізатися на винятки UI
Apfelkuacha

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". Якщо цей домен програми не є доменним додатком за замовчуванням, подія також може бути підвищена в домені програми за замовчуванням.


копіювання з 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, або використовуйте калькування. Нам потрібно знати більше деталей, наприклад, про які винятки, про які ви говорите, і які частини вашої програми можуть призвести до викидів виключень.


2
Справа в тому, що я хочу продовжувати роботу програми навіть після невиконаних винятків. Я просто хочу записати їх (у нас є спеціальна рамка ведення журналу для цього, виходячи з наших потреб) і мені не потрібно переривати всю програму лише тому, що якийсь плагін зробив щось дивне в коді взаємодії з користувачем. З того, що я бачив досі, неважливо чітко виокремити причини, оскільки різні частини насправді не знають одне одного.
Joey

9
Я розумію, але вам слід бути дуже обережними і надалі після винятку, який ви не досліджуєте, є можливість корупції даних тощо.
BobbyShaftoe

3
Я згоден з BobbyShaftoe. Це не правильний шлях. Нехай програма втратить роботу. Якщо помилка в якомусь плагіні, то виправте або накажіть когось виправити плагін. Залишити його працює надзвичайно небезпечно. Ви отримаєте дивні побічні ефекти і досягнете точки, коли не зможете знайти логічне пояснення помилок, які відбуваються у вашій програмі.

1
Зараз я трохи детальніше запитав це питання. Я знаю, що пов'язані з ризиками, і для цієї конкретної програми він вважався прийнятним. І переривання програми для чогось такого простого, як індекс поза межами, в той час як користувальницький інтерфейс намагався зробити приємну анімацію, це зайве і непотрібне. Так, я не знаю точної причини, але у нас є дані для підтвердження твердження, що переважна більшість випадків помилок є доброякісними. Серйозні, які ми маскуємо, можуть призвести до збоїв у програмі, але це все-таки сталося б без обліку глобальних винятків.
Joey

Це має бути коментар, а не відповідь. Відповідь на те, "як мені це зробити" з "ви, мабуть, не повинні" - це не відповідь, це коментар.
Джош Ное
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.