Следует ли использовать как AppDomain.UnhandledException, так и Application.DispatcherUnhandledException?

Прочитав несколько отличных сообщений о разнице между AppDomain.UnhandledException и Application.DispatcherUnhandledException, я понял, что должен обрабатывать оба. Это связано с тем, что пользователь с гораздо большей вероятностью сможет восстановиться после исключения, созданного основным потоком пользовательского интерфейса (т. е. Application.DispatcherUnhandledException). Правильный?

Кроме того, должен ли я также дать пользователю возможность продолжить программу для обоих или только для Application.DispatcherUnhandledException?

Пример кода ниже обрабатывает как AppDomain.UnhandledException, так и Application.DispatcherUnhandledException, и оба дают пользователю возможность попытаться продолжить, несмотря на исключение.

[спасибо, и часть приведенного ниже кода взята из других ответов]

App.xaml

<Application x:Class="MyProgram.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         Startup="App_StartupUriEventHandler"
         Exit="App_ExitEventHandler"
         DispatcherUnhandledException="AppUI_DispatcherUnhandledException">
    <Application.Resources>
    </Application.Resources>
</Application>

App.xaml.cs [отредактировано]

/// <summary>
/// Add dispatcher for Appdomain.UnhandledException
/// </summary>
public App()
    : base()
{
    this.Dispatcher.UnhandledException += OnDispatcherUnhandledException;
}

/// <summary>
/// Catch unhandled exceptions thrown on the main UI thread and allow 
/// option for user to continue program. 
/// The OnDispatcherUnhandledException method below for AppDomain.UnhandledException will handle all other exceptions thrown by any thread.
/// </summary>
void AppUI_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
    if (e.Exception == null)
    {
        Application.Current.Shutdown();
        return;
    }
    string errorMessage = string.Format("An application error occurred. If this error occurs again there seems to be a serious bug in the application, and you better close it.\n\nError:{0}\n\nDo you want to continue?\n(if you click Yes you will continue with your work, if you click No the application will close)", e.Exception.Message);
    //insert code to log exception here
    if (MessageBox.Show(errorMessage, "Application User Interface Error", MessageBoxButton.YesNoCancel, MessageBoxImage.Error) == MessageBoxResult.No)
    {
        if (MessageBox.Show("WARNING: The application will close. Any changes will not be saved!\nDo you really want to close it?", "Close the application!", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning) == MessageBoxResult.Yes)
        {
            Application.Current.Shutdown();
        }
    }
    e.Handled = true;
}

/// <summary>
/// Catch unhandled exceptions not thrown by the main UI thread.
/// The above AppUI_DispatcherUnhandledException method for DispatcherUnhandledException will only handle exceptions thrown by the main UI thread. 
/// Unhandled exceptions caught by this method typically terminate the runtime.
/// </summary>
void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e)
{
    string errorMessage = string.Format("An application error occurred. If this error occurs again there seems to be a serious bug in the application, and you better close it.\n\nError:{0}\n\nDo you want to continue?\n(if you click Yes you will continue with your work, if you click No the application will close)", e.Exception.Message);
    //insert code to log exception here
    if (MessageBox.Show(errorMessage, "Application UnhandledException Error", MessageBoxButton.YesNoCancel, MessageBoxImage.Error) == MessageBoxResult.No)
    {
        if (MessageBox.Show("WARNING: The application will close. Any changes will not be saved!\nDo you really want to close it?", "Close the application!", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning) == MessageBoxResult.Yes)
        {
            Application.Current.Shutdown();
        }
    }
    e.Handled = true;
}

person Kyle    schedule 08.04.2012    source источник


Ответы (2)


  • AppDomain.CurrentDomain.UnhandledException теоретически перехватывает все исключения во всех потоках домена приложения. Однако я обнаружил, что это очень ненадежно.
  • Application.Current.DispatcherUnhandledException перехватывает все исключения в потоке пользовательского интерфейса. Кажется, это работает надежно и заменит обработчик AppDomain.CurrentDomain.UnhandledException в потоке пользовательского интерфейса (имеет приоритет). Используйте e.Handled = true, чтобы приложение продолжало работать.

  • Для перехвата исключений в других потоках (в лучшем случае они обрабатываются в своем собственном потоке) я обнаружил, что System.Threading.Tasks.Task (только .NET 4.0 и выше) не требует особого обслуживания. Обработка исключений в задачах методом .ContinueWith(...,TaskContinuationOptions.OnlyOnFaulted). Подробности смотрите в моем ответе здесь.

person Mike Fuchs    schedule 22.10.2012
comment
Не могли бы вы уточнить, что .UnhandledException ненадежно? - person Brondahl; 10.12.2019
comment
@Brondahl Иногда казалось, что это работает (введите обработчик исключений), а иногда нет. Я недавно не проверял, так это или нет. - person Mike Fuchs; 17.06.2021

Обработчик AppDomain.UnhandledException подключен как:

AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

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

Лучше обработать Application.Current.DispatcherUnhandledException и проверить на CommunicationObjectFaultedException - так как вы можете исправить это, просто повторно инициализировав свой прокси - точно так же, как вы сделали при первоначальном подключении. Например:

void Current_DispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) {
    if (e.Exception is CommunicationObjectFaultedException) { //|| e.Exception is InvalidOperationException) {
        Reconnect();
        e.Handled = true;
    }
    else {
        MessageBox.Show(string.Format("An unexpected error has occured:\n{0}.\nThe application will close.", e.Exception));
        Application.Current.Shutdown();
    }
}

public bool Reconnect() {
    bool ok = false;
    MessageBoxResult result = MessageBox.Show("The connection to the server has been lost.  Try to reconnect?", "Connection lost", MessageBoxButton.YesNo);
    if (result == MessageBoxResult.Yes)
        ok = Initialize();
    if (!ok)
        Application.Current.Shutdown();
}

где Initialize содержит исходный код создания/подключения прокси-сервера.

В коде, который вы разместили выше, я подозреваю, что вы обрабатываете DispatcherUnhandledException дважды - подключая обработчик в xaml И в коде.

person Ricibob    schedule 08.04.2012
comment
Я также не уверен, есть ли возможность обработки исключения для потока пользовательского интерфейса, но я подозреваю, что это может произойти. Вот почему они оба имеют e.handled = true в конце - это должно предотвратить повторную обработку. - person Kyle; 09.04.2012