Sandra
Sandra

Reputation: 708

Catch-all Exception Handler for non-UI Threads in WPF

Specifically, I'm using WPF with MVVM. I have a MainWindow, which is a WPF Window where all of the action happens. It uses a corresponding View Model class for its properties, commands, etc.

I have set up main UI thread and non-UI thread exception handlers in Application.xaml.vb StartUp like this:

Private Sub Application_DispatcherUnhandledException(sender As Object, e As Windows.Threading.DispatcherUnhandledExceptionEventArgs) Handles Me.DispatcherUnhandledException
    ' catches main UI thread exceptions only
    ShowDebugOutput(e.Exception)
    e.Handled = True
End Sub

Private Sub Application_Startup(sender As Object, e As StartupEventArgs) Handles Me.Startup
    ' catches background exceptions
    Dim currentDomain As AppDomain = AppDomain.CurrentDomain
    AddHandler currentDomain.UnhandledException, AddressOf UnhandledExceptionHandler
    AddHandler System.Threading.Tasks.TaskScheduler.UnobservedTaskException, AddressOf BackgroundTaskExceptionHandler
End Sub

Sub UnhandledExceptionHandler(sender As Object, args As UnhandledExceptionEventArgs)
    Dim ex As Exception = DirectCast(args.ExceptionObject, Exception)
    ShowDebugOutput(ex)
End Sub

Sub BackgroundTaskExceptionHandler(sender As Object, args As System.Threading.Tasks.UnobservedTaskExceptionEventArgs)
    Dim ex As Exception = DirectCast(args.Exception, Exception)
    ShowDebugOutput(ex)
End Sub

This part works

When I try to test this out, by deliberately throwing an exception, it works. It is actually in the View Model in the Sub that handles the Select All button click.

The button:

<Button Content="Select All" Height="23" Width="110" Command="{Binding SelectAllCommand}" />

The Command where I'm throwing the exception that is successfully caught:

Private Sub SelectAll()
    Throw (New Exception("UI Thread exception"))
    SetAllApplyFlags(True)
End Sub

This part doesn't work

There's another button in the same MainWindow similarly bound to a command. However, it uses a Task to perform its work in the background, and an exception thrown in there does NOT get caught by my catch-all handlers.

Private Sub GeneratePreview()
    ' run in background
    System.Threading.Tasks.Task.Factory.StartNew(
        Sub()
            ' ... stuff snipped out, issue is the same with or without the rest of the code here ...
            Throw (New Exception("Throwing a background thread exception"))
        End Sub)
End Sub

There are several similar questions, but I haven't been able to actually figure out my answer from them. The AppDomain UnhandledException seems to be the answer in most cases, but it isn't for mine. What exactly do I have to add to be able to catch an exception that might be thrown in a non-UI thread this way?

What I ended up doing

I could not get the TaskScheduler.UnobservedTaskException event to call my event handler when I was handling it in Application.xaml.vb. But I took hints from the other answer, and I'll mark it as the answer because it ultimately helped.

However, it is not at the application level, so if this was a larger application, I'd have to duplicate this in every instance where I used a Task. This wasn't really what I was looking for, but not willing to spend more time on it now.

I ended up putting a try-catch inside the Task. In the catch, I was able to use Dispatcher.Invoke to still display a user-friendly dialog with the exception info.

Private Sub GeneratePreview()
    ' run in background
    System.Threading.Tasks.Task.Factory.StartNew(
        Sub()
            Try
                ' ... stuff snipped out, issue is the same with or without the rest of the code here ...
                Throw (New Exception("Throwing a background thread exception"))
            Catch ex As Exception
                Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, DirectCast(
                    Sub()
                        HRNetToADImport.Application.ShowDebugOutput(ex)
                    End Sub, Action))
            End Try
        End Sub)
End Sub

Upvotes: 2

Views: 1195

Answers (3)

Emmanuel DURIN
Emmanuel DURIN

Reputation: 4913

Sandra,

I read cscmh99 proposition, took your code,and try to run, and it works !

I mean you can subscribe to TaskScheduler.UnobservedTaskException.

  • Then you will catch UnobservedException

  • But you won't catch observed exceptions
    Observed exceptions are those from Tasks waited with .Wait() or .Result

Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
    ' Here follows an Unobserved Exception

    System.Threading.Tasks.Task.Factory.StartNew(
        Sub()
            Throw (New Exception("Throwing a background thread exception"))
        End Sub)

    ' Here follows an ObservedException
    ' ObservedException because there is call to .Wait() (or .Result)
    ' You can't use UnobservedException for that one

    Dim t As Task = System.Threading.Tasks.Task.Factory.StartNew(
        Sub()
            Throw (New Exception("Throwing a background thread exception"))
        End Sub)
    t.Wait()
End Sub

Here is code to working solution : http://1drv.ms/1XOvTbK

Regards

Upvotes: 1

cscmh99
cscmh99

Reputation: 2781

TaskScheduler.UnobservedTaskException Event is what you want to subscribe from App start.

https://msdn.microsoft.com/en-us/library/vstudio/system.threading.tasks.taskscheduler.unobservedtaskexception(v=vs.100).aspx?cs-save-lang=1&cs-lang=vb#code-snippet-1

Occurs when a faulted Task's unobserved exception is about to trigger exception escalation policy, which, by default, would terminate the process.

This AppDomain-wide event provides a mechanism to prevent exception escalation policy (which, by default, terminates the process) from triggering.

NOTE: The event might not be fired right away (possible a few second delay). You could imagine there's some operations of call stack bubbling and context switching of normal exception operation before ended up reaching the UnobservedTaskException event.

One thing I want to point out is that, it's a must to wrap your whole application with generic exception handler to prevent application being terminate. But, please do remember that it's also a must to implement proper exception handling to all paths that might throw exception.

Upvotes: 1

Emmanuel DURIN
Emmanuel DURIN

Reputation: 4913

Sandra,

There is a way to catch the exception from inside your background Task.
I admit my solution is not global, but at least it catches and prevents crash !

Private Async Sub Button_Click(sender As Object, e As RoutedEventArgs)
    Dim t As Task = Task.Factory.StartNew(
       Sub()
           ' ... stuff snipped out, issue is the same with or without the rest of the code here ...
           Throw (New Exception("Throwing a background thread exception"))
       End Sub)
    Try
        Await t
    Catch ex1 As Exception
        Debug.WriteLine("Exception from inside task " & ex1.Message)
    End Try
End Sub

Think it could help, may be you, may be others.

Upvotes: 0

Related Questions