这一篇主要整理WPF中的异常处理.
WPF中的异常处理
类似于Task中对unobserved exception的处理, WPF中有Application.DispatcherUnhandledException事件监听那些unhandled exception. 关于这点, 官方链接提供了最重要的信息https://msdn.microsoft.com/en-us/library/system.windows.application.dispatcherunhandledexception(v=vs.110).aspx
By default, Windows Presentation Foundation (WPF) catches unhandled exceptions, notifies users of the exception from a dialog box (from which they can report the exception), and automatically shuts down an application.
However, if an application needs to perform custom unhandled exception processing from a centralized location, you should handle DispatcherUnhandledException.
DispatcherUnhandledException is raised by an Application for each exception that is unhandled by code running on the main UI thread.
If an exception is not handled on either a background user interface (UI) thread (a thread with its own Dispatcher) or a background worker thread (a thread without a Dispatcher), the exception is not forwarded to the main UI thread. Consequently, DispatcherUnhandledException is not raised. In these circumstances, you will need to write code to do the following:
Handle exceptions on the background thread.
Dispatch those exceptions to the main UI thread.
Rethrow them on the main UI thread without handling them to allow DispatcherUnhandledException to be raised.
先看background worker thread中抛出异常的捕获1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Title = $"Running on Main UI Thread {Thread.CurrentThread.ManagedThreadId}";
}
private void startSecondaryWorkerThreadButton_Click(object sender, RoutedEventArgs e)
{
// Creates and starts a secondary thread in a single threaded apartment (STA)
var thread = new Thread(MethodRunningOnSecondaryWorkerThread);
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
// THIS METHOD RUNS ON A SECONDARY WORKER THREAD (THREAD WITHOUT A DISPATCHER)
private void MethodRunningOnSecondaryWorkerThread()
{
try
{
WorkerMethod();
}
catch (Exception ex)
{
// Dispatch the exception back to the main UI thread. Then, reraise
// the exception on the main UI thread and handle it from the handler
// the Application object's DispatcherUnhandledException event.
// 注意这里是Invoke和Send, 表示同步执行
var secondaryWorkerThreadId = Thread.CurrentThread.ManagedThreadId;
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Send,
(DispatcherOperationCallback) delegate
{
// THIS CODE RUNS BACK ON THE MAIN UI THREAD
string msg = $"Exception forwarded from secondary worker thread {secondaryWorkerThreadId}.";
throw new Exception(msg, ex);
}
, null);
// NOTE - Application execution will only continue from this point
// onwards if the exception was handled on the main UI thread.
// by Application.DispatcherUnhandledException
}
}
private void WorkerMethod()
{
// This method would do real processing on the secondary worker thread.
// For the purposes of this sample, it throws an exception
string msg =
$"Exception raised secondary on worker thread {Dispatcher.CurrentDispatcher.Thread.ManagedThreadId}.";
throw new Exception(msg);
}
}
再来看看对background ui thread中抛出异常的捕获, 从中也可以学习如何启动一个ui thread1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Title = $"Running on Main UI Thread {Thread.CurrentThread.ManagedThreadId}";
}
// THIS EVENT HANDLER RUNS ON THE MAIN UI THREAD
private void startSecondaryUIThreadButton_Click(object sender, RoutedEventArgs e)
{
// Creates and starts a secondary thread in a single threaded apartment (STA)
var thread = new Thread(MethodRunningOnSecondaryUIThread);
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
}
// THIS METHOD RUNS ON A SECONDARY UI THREAD (THREAD WITH A DISPATCHER)
private void MethodRunningOnSecondaryUIThread()
{
var secondaryUiThreadId = Thread.CurrentThread.ManagedThreadId;
try
{
// On secondary thread, show a new Window before starting a new Dispatcher
// ie turn secondary thread into a UI thread
var window = new SecondaryUIThreadWindow();
window.Show();
Dispatcher.Run();
}
catch (Exception ex)
{
// Dispatch the exception back to the main ui thread and reraise it
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Send,
(DispatcherOperationCallback) delegate
{
// THIS CODE RUNS BACK ON THE MAIN UI THREAD
string msg = $"Exception forwarded from secondary UI thread {secondaryUiThreadId}.";
throw new Exception(msg, ex);
}
, null);
// NOTE - Application execution will only continue from this point
// onwards if the exception was handled on the main UI thread
// by Application.DispatcherUnhandledException
}
}
}
public partial class SecondaryUIThreadWindow : Window
{
public SecondaryUIThreadWindow()
{
InitializeComponent();
Title = $"Running on Secondary UI Thread {Thread.CurrentThread.ManagedThreadId}";
}
private void raiseExceptionOnSecondaryUIThreadButton_Click(object sender, RoutedEventArgs e)
{
// Raise an exception on the secondary UI thread
string msg = $"Exception raised on secondary UI thread {Dispatcher.Thread.ManagedThreadId}.";
throw new Exception(msg);
}
private void SecondaryUiThreadWindow_Closed(object sender, EventArgs e)
{
// End this thread of execution
Dispatcher.InvokeShutdown();
}
}
The DispatcherUnhandledException event handler is passed a DispatcherUnhandledExceptionEventArgs argument that contains contextual information regarding the exception, including: 1.The exception (Exception). 2.The Dispatcher from which it originated (Dispatcher).
You can use this information to determine whether an exception is recoverable or not. A recoverable exception might be a FileNotFoundException, for example, while an unrecoverable exception might be a StackOverflowException, for example.
When you process an unhandled exception from DispatcherUnhandledException, and you don’t want WPF to continue processing it, you need to set the Handled property to true.
例如:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void raiseRecoverableException_Click(object sender, RoutedEventArgs e)
{
throw new DivideByZeroException("Recoverable Exception");
}
private void raiseUnecoverableException_Click(object sender, RoutedEventArgs e)
{
throw new ArgumentNullException(@"Unrecoverable Exception");
}
}
public partial class App : Application
{
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
// Process unhandled exception
var shutdown = false;
// Process exception
if (e.Exception is DivideByZeroException)
{
// Recoverable - continue processing
shutdown = false;
}
else if (e.Exception is ArgumentNullException)
{
// Unrecoverable - end processing
shutdown = true;
}
if (shutdown)
{
// If unrecoverable, attempt to save data
var result =
MessageBox.Show("Application must exit:\n\n" + e.Exception.Message + "\n\nSave before exit?", "app",
MessageBoxButton.YesNo, MessageBoxImage.Error);
if (result == MessageBoxResult.Yes)
{
// Save data
}
// Add entry to event log
EventLog.WriteEntry("app", "Unrecoverable Exception: " + e.Exception.Message, EventLogEntryType.Error);
// Return exit code
Shutdown(-1);
}
// Prevent default unhandled exception processing
e.Handled = true;
}
}
以上示例代码来自WPFSample(https://github.com/Microsoft/WPF-Samples)
其他参考:
Handling Unhandled Exceptions in WPF (The most complete collection of handlers)
(https://code.msdn.microsoft.com/windowsdesktop/Handling-Unhandled-47492d0b)Order in Chaos: Handling unhandled exceptions in a WPF application
(https://dzone.com/articles/order-chaos-handling-unhandled)Unhandled Exception Handler For WPF Applications(https://www.codeproject.com/articles/90866/unhandled-exception-handler-for-wpf-applications)