Windows Forms アプリケーションでは未処理の例外を Application.ThreadExceptionイベントや AppDomain.UnhandledException イベントで処理することができました。また Visual Basic のアプリケーション フレームワークでは WindowsFormsApplicationBase.UnhandledException イベントで処理していました。WPF アプリケーションではどのように未処理の例外を処理できるのでしょうか。
Application.DispatcherUnhandledException イベント
WPF では Application.DispatcherUnhandledException イベントで未処理の例外を処理することができます。これは WindowsFormsApplicationBase.UnhandledException イベントに似ています。未処理の例外を処理しなかった場合は、既定のウィンドウが表示されアプリケーションが終了します(既定のウィンドウといっても、これは OS 側で表示しているウィンドウだと思います)。
DispatcherUnhandledException イベントでは発生した例外の情報と、例外の発生元の Dispatcher の情報を取得することができます。このイベントの引数の Handled プロパティを True にすることで、アプリケーションを終了しないようにできます。独自のメッセージを表示してからアプリケーションを終了する場合にも、Handled を True にしてから Application.Shutdown メソッドを呼びます。そうしないと既定のウィンドウが表示されてしまいます。
Class Application
Private Sub Application_DispatcherUnhandledException(ByVal sender As System.Object, ByVal e As System.Windows.Threading.DispatcherUnhandledExceptionEventArgs)
MessageBox.Show(e.Exception.ToString)
e.Handled = True
Me.Shutdown()
End Sub
End Class
このイベントで全ての未処理の例外をキャッチできるわけではありません。キャッチできる例外は次のものです。
- Application クラスが実行しているスレッドで発生した未処理の例外
Application クラスが実行しているスレッドはメイン UI スレッドです。そのスレッド上で発生した例外のみキャッチすることができます。そのため、次のような例外はキャッチすることができません。
バックグラウンドで処理を行うためなどに新たにスレッドを生成した場合、そのスレッドで発生した例外をキャッチすることはできません。一応、次のようにすればキャッチすることができます。
Imports System.Threading
Imports System.Windows.Threading
Class Window1
Private Sub Button1_Click(...) Handles Button1.Click
Dim t As New Thread(New ThreadStart(AddressOf ThrowSecondaryThreadException))
t.Start()
End Sub
Private Sub ThrowSecondaryThreadException()
Try
Throw New Exception("セカンダリスレッドで例外を発生させます。")
Catch ex As Exception
Application.Current.Dispatcher.Invoke(
DispatcherPriority.Send,
New DispatcherOperationCallback(AddressOf ThrowMainThreadException),
ex)
End Try
End Sub
Private Function ThrowMainThreadException(ByVal arg As Object) As Object
Throw New Exception("メイン UI スレッドで例外を再スローします。", CType(arg, Exception))
End Function
End Class
このようにセカンダリスレッド上で例外をキャッチし、それをメイン UI スレッド上に渡し、そこから例外を再スローする必要があります。なんだか例外をキャッチしている時点で、未処理の例外という感じがしませんが・・・。
実はこんなことをしなくても Windows Forms で使用していた AppDomain.UnhandledException イベントを使用すれば、セカンダリスレッドで発生した例外をキャッチすることができます。ただし AppDomain.UnhandledException イベントで例外をキャッチしても、既定のウィンドウが表示されアプリケーションは終了してしまいます。
まとめ
- 未処理の例外を処理しない場合は、アプリケーションが終了する。
- Application.DispatcherUnhandledException イベントでメイン UI スレッド上の未処理の例外を処理することができる。
- AppDomain.UnhandledException イベントでセカンダリスレッド上から発生した例外をキャッチすることができるが、アプリケーションは終了する。
WPF の未処理の例外の処理は WindowsFormsApplicationBase.UnhandledException イベントが DispatcherUnhandledException イベントに置き換わっている思えば、違いはほとんどないと思います。もし AppDomain.UnhandledException イベントが Application クラスにあれば、未処理の例外がもっと扱いやすくなったのでは?と思いました。
それと一つ気になったのですが、DispatcherUnhandledException イベントの引数にある Dispatcher は何のためにあるのでしょうか? キャッチできる例外は Application クラスと同じスレッドの例外のため Application.Current.Dispatcher で取得できると思うのですが・・・。