2

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
svick
  • 236,525
  • 50
  • 385
  • 514
Sandra
  • 608
  • 2
  • 11
  • 23
  • If this is 4.5, you need https://msdn.microsoft.com/en-us/library/system.threading.tasks.taskscheduler.unobservedtaskexception(v=vs.110).aspx –  Oct 12 '15 at 20:39
  • Possible duplicate. http://stackoverflow.com/questions/1472498/wpf-global-exception-handler ... I think the answer from @Drew Noakes is the best answer over there. – cscmh99 Oct 13 '15 at 02:56
  • Aha. I saw that SO question, but didn't trigger on the TaskScheduler.UnobservedTaskException part. So could I add this handler in the Application.xaml.vb - one handler for the entire application? – Sandra Oct 13 '15 at 18:58
  • @cscmh99 could you please elaborate how I can use it? I tried adding a handler in Application.xaml.vb in StartUp: AddHandler System.Threading.Tasks.TaskScheduler.UnobservedTaskException, AddressOf BackgroundTaskExceptionHandler, but it still doesn't get called.when I throw that background exception. – Sandra Oct 19 '15 at 19:05
  • Can you wait longer to see if the event trigger ? The event might not be fired right away (possible a few second delay). Sorry that I'm not really a VB person. But i test it in C# just like what you did and it did work. Also register the event in App constructor. – cscmh99 Oct 20 '15 at 01:18

3 Answers3

1

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.

Community
  • 1
  • 1
cscmh99
  • 2,701
  • 2
  • 15
  • 18
  • Thank you. I've edited my post to include the handler I've added. I also ran the compiled program to avoid Visual Studio stopping on the unhandled exception, but no difference. The handler does not fire no matter how long I wait. If you have code in C# no worries, I can translate. – Sandra Oct 20 '15 at 10:44
1

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

Emmanuel DURIN
  • 4,803
  • 2
  • 28
  • 53
  • Ha. My code doesn't work for me, yet I don't see the difference between what I did and what you have in your sample. My exception should be unobserved, since I'm not waiting for it, and I have subscribed to the UnobservedTaskException. The only thing I can think of is that in my case, I'm not throwing from code-behind, but from the ViewModel. Perhaps the scope of the event when it fires is not broad enough to reach the handler in Application.xaml.vb. Or... WithEvents! Since it's an event... That might be it. Will try next week. Thanks! – Sandra Nov 05 '15 at 11:12
  • I am trying to figure out : Are you running another AppDomain ? You say in your code is in ViewModel, ok - is it a Task inside another ( So it could be another TaskScheduler) ? – Emmanuel DURIN Nov 05 '15 at 12:53
0

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.

Emmanuel DURIN
  • 4,803
  • 2
  • 28
  • 53
  • Thank you, I'm sure it will help others. In my case, I'd really like to do it globally. I've already got exception catching for all the places I think may get one. This is for anything unexpected I may have missed. – Sandra Oct 20 '15 at 10:39