Ответы, включающие событие AppDomain.UnhandledException
, вероятно, предполагают, что любое необработанное исключение возникает в потоке пользовательского интерфейса WPF.Это означает, что, хотя они часто работают, они формально не безопасны для использования с операциями в других потоках.Более надежный вариант - Application.DispatcherUnhandledException
, который WPF предоставляет приложениям для реализации пользовательских отчетов о необработанных исключениях в основном потоке пользовательского интерфейса, фоновых потоках пользовательского интерфейса и BackgroundWorker
экземплярах.
Вероятная точка отказа с AppDomain.UnhandledException
- это диалоговое окно отчета, вероятно, требующее однопотокового размещения (STA), так как и WPF, и Windows Forms являются STA.Поскольку потоки пула потоков по умолчанию используют многопоточные квартиры, необработанное исключение из асинхронной операции в Task.Run()
, ThreadPool.QueueUserWorkItem()
, IProgress<T>.Report()
или многоаналогичные API-интерфейсы могут привести к сбою диалогового окна отчета с чем-то похожим на исключение ниже.Это приводит к сбою приложения без шанса побудить пользователя сообщить об основной проблеме.
System.InvalidOperationException: The calling thread must be STA, because many UI components require this.
at System.Windows.Input.InputManager..ctor()
at System.Windows.Input.InputManager.GetCurrentInputManagerImpl()
at System.Windows.Input.KeyboardNavigation..ctor()
at System.Windows.FrameworkElement.FrameworkServices..ctor()
at System.Windows.FrameworkElement.EnsureFrameworkServices()
at System.Windows.FrameworkElement..ctor()
at System.Windows.Controls.Control..ctor()
at System.Windows.Window..ctor()
Мой опыт использования Application.DispatcherUnhandledException
является надежным в сочетании с TPL как Task
и связанные с ним классы облегчают распространение исключений обратно к вызывающей стороне.Для суммирования обработки исключений TPL , Wait()
и await
автоматически перебрасываются, и вызывающие абоненты, использующие другие методы синхронизации, должны проверить Task.Exception
.
Однако, как *В документации 1038 * указывается, что в других случаях требуется, чтобы вызывающий WPF реализовывал распространение исключений.Возможно, наиболее распространенным из них является WPF Progress<T>
, который, как ни странно, не поддерживает распространение исключений из-за его реализации IProgress<T>.Report()
, хотя его единственной целью является передача информации о прогрессе от работникатемы обратно в интерфейс.Один из способов - обернуть обработчик обновления прогресса, используя подход, подобный примеру ниже.Это всего лишь эскиз;более частый опрос свойства Exception
может быть полезен для остановки при ошибках, более сильная семантика IDisposable
может быть предпочтительнее End()
, и это может быть полезно для обработки случаев, когда отставаниеобновления по-разному завершаются ошибкой.
public class ExceptionPropagatingProgress<TProgress>
{
private readonly Action<TProgress> onProgressUpdateCore;
private readonly IProgress<TProgress> progress;
public Exception Exception { get; private set; }
public ExceptionPropagatingProgress(Action<TProgress> handler)
{
this.Exception = null;
this.onProgressUpdateCore = handler ?? throw new ArgumentNullException(nameof(handler));
this.progress = new Progress<TProgress>(this.OnProgressUpdate);
}
public void End()
{
if (this.Exception != null)
{
throw new AggregateException(this.Exception);
}
}
private void OnProgressUpdate(TProgress value)
{
try
{
this.onProgressUpdateCore(value);
}
catch (Exception exception)
{
lock (this.onProgressUpdateCore)
{
if (this.Exception == null)
{
this.Exception = exception;
}
else
{
this.Exception = new AggregateException(this.Exception, exception);
}
}
}
}
public void QueueProgressUpdate(TProgress value)
{
if (this.Exception != null)
{
throw new AggregateException(this.Exception);
}
this.progress.Report(value);
}
}