Задать вопрос
@romaro

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

Сейчас обработка исключений в моем приложении организована следующим образом. Есть кастомный обработчик, который доступен во всех вью-моделях через DI. Он используется для "ожидаемых" исключений, после которых приложение может продолжить работу. Одновременно с этим у меня есть "финальный" обработчик для неизвестных исключений, после которых работу приложения следует завершить (нет гарантий, что приложение будет работать стабильно).

Точка входа в приложение выглядит примерно так:
public class Program
    {
        [STAThread]
        public static void Main()
        {
            // Message Helper
            var messageHelper = new MessageHelper();

            // Exception Handler
            var exceptionHandler = new MvvmExceptionHandler(messageHelper);

            try
            {
                var mainViewModel = new MainViewModelBuilder()
                    .WithExceptionHandler(exceptionHandler)
                    .Build();

                var mainWindow = new MainWindow
                {
                    DataContext = mainViewModel
                };

                var app = new App(mainWindow)
                {
                    MainWindow = mainWindow,
                    ShutdownMode = System.Windows.ShutdownMode.OnMainWindowClose
                };
                app.Run();
            }
            catch (Exception ex)
            {
                //MessageBox.Show("Application will be closed.", "Fatal error", MessageBoxButton.OK, MessageBoxImage.Error);
                App.Current.Shutdown();
            }
        }
    }


Собственно, у меня 3 связанных вопроса:
1) Нужно ли мне обрабатывать специфические для WPF исключения, т.е. подписываться на события следующих классов.
AppDomain.CurrentDomain.UnhandledException
Dispatcher.UnhandledException
Application.Current.DispatcherUnhandledException
TaskScheduler.UnobservedTaskException

Либо я смогу поймать все эти исключения в корневом try...catch?

2) Как правильно вывести пользователю сообщение о фатальной ошибке перед закрытием программы? Где-то читал, что использование WinAPI на этом уровне может заблокировать освобождение ресурсов.

3) Как корректно завершить работу приложения? Сейчас я делаю это через Application.Current.Shutdown(). Это универсальный вариант? Т.е. подходит ли для ошибок, который произошли за пределами UI-треда?
  • Вопрос задан
  • 51 просмотр
Подписаться 1 Средний Комментировать
Пригласить эксперта
Ответы на вопрос 1
VoidVolker
@VoidVolker Куратор тега C#
Dark side eye. А у нас печеньки! А у вас?
  1. Да, надо. Нет, все исключения в Main поймать нельзя.
  2. Есть несколько вариантов:
    1. Сообщение в стандартном WPF контроле в рамках основного окна (типа стандартного сообщения или всплывающего контрола).
    2. Стандартное WinAPI сообщение.
    3. Отдельное окно с WPF контролом и деталями сообщения.
  3. Да, вполне нормальный вариант.

В целом же, стандартный подход выглядит примерно так:
  • Создаём несколько категорий исключений для разных ситуаций (условно - диск, приложение, загрузка, скачивание, сеть и т.п.)
  • В каждой категории создаём несколько типов исключений, наследуя их от класса Exception и указывая параметры исключений
  • На верхнем уровне ловим исключения, для каких-то определённых типов исключений можно добавлять отдельные ветки
  • В обычных условиях просто выкидываем исключение, которое будет поймано на верхнем уровне
  • В нестандартных условиях ловим на месте или показываем сообщение об ошибке с ожиданием

Это позволяет по типу или идентификатору исключения определить точное место, где это исключение произошло, а так же отделить свои исключения от чужих.

Реальный пример базового класса для исключения с локализацией:
    /// <summary>
    /// Localized exception
    /// </summary>
    /// <param name="stringId">i18n string Id</param>
    /// <param name="args">String arguments for format</param>
    public class I18nException(string stringId, object[] args)
        : Exception(
            string.Format(
                I18n($"{ExceptionId} {stringId}"),
                args))
    {
        public readonly object[] Args = args;
        public readonly string StringId = stringId;
        private const string ExceptionId = "Exception";
    }

I18n(string str) - функция для получения локализованной строки для текущего языка приложения.
Ну и далее наследуем свои исключения от этого базового типа:
    public class ElementNotFoundException(Type type, string name)
        : I18nException(
            "Element not found",
            [type.FullName, name]
        )
    {
        public readonly string ElementName = name;
        public readonly Type ElementType = type;
    }
(тыц).
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы