Exception reporting from a WPF Application

后端 未结 4 1679
刺人心
刺人心 2021-02-05 13:09

So lets say I happen to have an unhandled exception in my application or it crashes for some reason. Is there some way for me to capture the output and show an error report dial

4条回答
  •  Happy的楠姐
    2021-02-05 13:37

    The answers involving the AppDomain.UnhandledException event probably make an implicit assumption any unhandled exception is raised on WPF's UI thread. This means that, while they'll often work, they're not formally safe for use with operations on other threads. A more reliable option is Application.DispatcherUnhandledException, which WPF provides for applications to implement custom reporting of unhandled exceptions across the main UI thread, background UI threads, and BackgroundWorker instances.

    A likely failure point with AppDomain.UnhandledException is the report dialog probably requires a single-threaded apartment (STA) since both WPF and Windows Forms are STA. As thread pool threads default to multithreaded apartments, an unhandled exception from an asynchronous operation under Task.Run(), ThreadPool.QueueUserWorkItem(), IProgress.Report(), or the many similar APIs will result upon instantiation of a report dialog failing with something like the exception below. This causes an application crash without a chance to prompt the user to report the underlying problem.

    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()
    

    My experience of Application.DispatcherUnhandledException is it's robust in combination with the TPL as Task and its associated classes facilitate propagation of exceptions back to the caller. To summarize the TPL's exception handling, Wait() and await rethrow automatically and callers using other synchronization methods should check Task.Exception.

    However, as Application.DispatcherUnhandledException's documentation points out, other cases require WPF's caller to implement exception propagation. Perhaps the most common of these is WPF's Progress, which strangely lacks support for propagating exceptions from within its implementation of IProgress.Report() even though its sole purpose is moving progress information from worker threads back to the UI. One workaround is to wrap the progress update handler using an approach similar to the example below. This is just a sketch; more frequent polling of the Exception property may be valuable in halting on errors, the stronger semantics of IDisposable may be preferable to End(), and it might be useful to handle cases where a backlog of updates fail concurrently differently.

    public class ExceptionPropagatingProgress
    {
        private readonly Action onProgressUpdateCore;
        private readonly IProgress progress;
    
        public Exception Exception { get; private set; }
    
        public ExceptionPropagatingProgress(Action handler)
        {
            this.Exception = null;
            this.onProgressUpdateCore = handler ?? throw new ArgumentNullException(nameof(handler));
            this.progress = new Progress(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);
        }
    }
    

提交回复
热议问题