What is the recommended way to handle TaskScheduler.UnobservedTaskException in GUI apps?
-
Saturday, September 01, 2012 8:30 AM
For robustness I want to add a handler for TaskScheduler.UnobservedTaskException.
The handler does log Exception(s) inform the user and shutdown the app.
Does the following implementation make sense?
Is it OK in WPF to show a MessageBox or a Windows in this handler, or is this a bad idea because we are running in the finalizers of unobserved Tasks?Private Sub TaskSheduler_UnobservedTaskException(sender As Object, e As UnobservedTaskExceptionEventArgs) Static _wasUserInformed As Boolean = False e.SetObserved() 'Trace all Exceptions e.Exception.Flatten.Handle(Function(ex As Exception) 'TODO trace and log Exception Debug.Print("UnobservedTaskException: {0}", ex.Message) Return True End Function) If Not _wasUserInformed Then 'Show root Exception _wasUserInformed = True Application.Current.Dispatcher.BeginInvoke(Sub() 'MessageBox.Show(e.Exception.GetBaseException.Message) Dim win As New UnexpectedExceptionWindow win.UnexpectedException = e.Exception.GetBaseException win.ShowDialog() Application.Current.Dispatcher.BeginInvoke(Sub() Application.Current.Shutdown()) End Sub) End If End Sub
All Replies
-
Saturday, September 08, 2012 2:24 PM
You'd better not count on the TaskScheduler.UnobservedTaskException event. Because it doesn't guarantee that it can catches all unobserved task exceptions. That's because the Task class catches unhandled exceptions internally and tries to fire it again itself (manually) when calling Task.Wait() or accessing Task<T>.Result. Otherwise it will fire the event when it is being 'garbage collected' and finalized. However if task finalization happens when the application is already shutting down, the Task class desists firing the unhandled exception. So TaskScheduler.UnobservedTaskException will not be raised and you will lose that exception.
Also preventing the application from closing by calling SetObserved() in TaskScheduler.UnobservedTaskException which lets the user continues using the app based on an unreliable situation is not a good practice. You'd better do your job (logging the exception and the state of the app e.g.) and let the app being shut down.
There are also some other events which may also help you to work with unhandled exceptions:
- Application.ThreadException (Windows Forms)
- Application.DispatcherUnhandledException (WPF)
- AppDomain.UnhandledException (any .NET application)
Pay attention that the handlers of these events are raised on a thread different than the main thread. So in a GUI app, you can not access UI elements in the handler and you need something like Control.Invoke()/BeginInvoke() or Dispatcher.Invoke()/BeginInvoke() (the same way you've used Dispatcher.BeginInvoke() in your code snippet) or use SynchronizationContext class.
Also keep in mind that the first two events only raised when an unhandled exception is happened in the GUI threads. In non-GUI thread your only hope is AppDomain.UnhandledException.- Edited by Mansoor Omrani Saturday, September 08, 2012 2:32 PM
-
Saturday, September 08, 2012 6:07 PM
Below my current implementation. Meanwhile I made sure to get off the finalizer thread when handling the UnobservedTaskExceptions and do unwrap rethrown AggregateExceptions.
Class Application Private WithEvents TaskScheduler As TaskScheduler = TaskScheduler.Current Private WithEvents CurrentDomain As AppDomain = AppDomain.CurrentDomain Private _eventLog As New EventLog("Application", ".", My.Application.Info.AssemblyName) Private Sub Application_DispatcherUnhandledException(sender As Object, e As Windows.Threading.DispatcherUnhandledExceptionEventArgs ) Handles Me.DispatcherUnhandledException Dim ex = UnwrapRethrownAggregateException(e.Exception) LogException(ex) NotifyUser(ex) e.Handled = True Application.Current.Shutdown() End Sub Private Sub CurrentDomain_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs ) Handles CurrentDomain.UnhandledException Dim ex = UnwrapRethrownAggregateException(e.ExceptionObject) LogException(ex) NotifyUser(ex) Application.Current.Shutdown() End Sub Private Sub TaskScheduler_UnobservedTaskException(sender As Object, e As UnobservedTaskExceptionEventArgs ) Handles TaskScheduler.UnobservedTaskException Static _wasUserInformed As Boolean = False 'Free the finalizer thread and execute on the UI thread to be able to inform user Dispatcher.BeginInvoke(Sub() LogException(e.Exception)) e.SetObserved() If Not _wasUserInformed Then _wasUserInformed = True 'Show first error Dispatcher.BeginInvoke(Sub() NotifyUser(e.Exception) Application.Current.Shutdown() End Sub) End If End Sub Private Function UnwrapRethrownAggregateException(ex As Exception) As Exception 'Hacky, but did not find a better solution Const ExceptionThrownByTargetOfInvocation = -2146232828 If ex.HResult = ExceptionThrownByTargetOfInvocation AndAlso TypeOf (ex.InnerException) Is AggregateException Then Return ex.InnerException Else Return ex End If End Function Private Sub LogException(ex As Exception) On Error Resume Next If TypeOf (ex) Is AggregateException Then CType(ex, AggregateException).Flatten.Handle( Function(ie As Exception) TraceException(ie) Return False End Function) WriteToEventLog(CType(ex, AggregateException).GetBaseException) Else TraceException(ex) WriteToEventLog(ex) End If End Sub Private Sub WriteToEventLog(ex As Exception) On Error Resume Next _eventLog.WriteEntry(ex.Message, EventLogEntryType.Error) End Sub Private Sub TraceException(ex As Exception) On Error Resume Next Debug.Print(String.Format("Unexpected Exception: {0}", ex.ToString)) '_trace.ErrorFormat("Unexpected Exception: {0}", ex.ToString) End Sub Private Sub NotifyUser(ex As Exception) On Error Resume Next If TypeOf (ex) Is AggregateException Then MessageBox.Show(CType(ex, AggregateException).GetBaseException.Message, "Unexpected Error") Else MessageBox.Show(ex.Message, "Unexpected Error") End If End Sub End Class

