73

My WinForms app uses a number of BackgroundWorker objects to retrieve information from a database. I'm using BackgroundWorker because it allows the UI to remain unblocked during long-running database queries and it simplifies the threading model for me.

I'm getting occasional DatabaseExceptions in some of these background threads, and I have witnessed at least one of these exceptions in a worker thread while debugging. I'm fairly confident these exceptions are timeouts which I suppose its reasonable to expect from time to time.

My question is about what happens when an unhandled exception occurs in one of these background worker threads.

I don't think I can catch an exception in another thread, but can I expect my WorkerCompleted method to be executed? Is there any property or method of the BackgroundWorker I can interrogate for exceptions?

Ed Guiness
  • 34,602
  • 16
  • 110
  • 145

5 Answers5

79

If the operation raises an exception that your code does not handle, the BackgroundWorker catches the exception and passes it into the RunWorkerCompleted event handler, where it is exposed as the Error property of System.ComponentModel.RunWorkerCompletedEventArgs. If you are running under the Visual Studio debugger, the debugger will break at the point in the DoWork event handler where the unhandled exception was raised.

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.dowork.aspx

Ed Guiness
  • 34,602
  • 16
  • 110
  • 145
  • 5
    That'll do it. Of course, you could always catch the exception in your DoWork method, and react accordingly (remembering to use Control.Invoke if you are going to update the UI). But RunWorkerCompleted is indeed simpler. – Marc Gravell Nov 03 '08 at 13:54
  • I am finding this solution. My RunWorkerCompleted does not run when I simply Throw New Exception() but Unhandled Exception raised. catch in DoWork is not the real answer. There should be something wrong in my BackgroundWorker. – CallMeLaNN Apr 14 '10 at 09:04
  • I've just spent the last seven hours trying to figure out why my app won't run and just exited, and all because an error occurred in BackgroundWorker that wasn't being caught correctly. +1 to you and I think you deserve a beer as well :-) – EvilDr Jan 16 '13 at 14:44
  • 1
    @EvilDr Thanks, I like real ale. – Ed Guiness Jan 16 '13 at 14:52
  • Assuming you're in Bedford, if I get there I'll honour that. Or if work ever brings you to North Yorkshire I'll honour it here as well :-) – EvilDr Jan 16 '13 at 19:26
  • Thank you for adding the line: "If you are running under the Visual Studio debugger, the debugger will break at the point in the DoWork event handler where the unhandled exception was raised." I thought my code was executing incorrectly because the debugger was barking at me when I threw an exception from the BackgroundWorker thread, but I guess VS just displays a dialog (by default) in this case, even though the app DOES handle it gracefully? – Doug Oct 08 '14 at 03:44
34

I am fully using BackgroundWorker over a years and really know it in deep.

Just recently, My RunWorkerCompleted does not catch the e.Error when I simply Throw New Exception("Test") in DoWork. However Unhandled Exception raised. Catch in DoWork is not the best practice thus e.Error got no meaning.

When I try to create new Form with new BackgroundWorker, e.Error in RunWorkerCompleted handled successfully. There should be something wrong in my complicated BackgroundWorker.

After a few days googling and debugging, trying an error. I found this in my RunWorkerCompleted:

  • Check for e.Error first, then e.Cancelled and lastly e.Result
  • Do not get the e.Result if e.Cancelled = True.
  • Do not get the e.Result if e.Error is not null (or Nothing) **

** This is where I miss. If you trying to use e.Result if e.Error is not null (or Nothing), Unhandled Exception will thrown.


UPDATE: In the e.Result get property .NET design it to check for e.Error first, if got error, then they will re-throw the same exception from DoWork. That is why we get Unhandled exception in RunWorkerCompleted but actually the exception is come from DoWork.

Here is the best practice to do in RunWorkerCompleted:

If e.Error IsNot Nothing Then
  ' Handle the error here
Else
  If e.Cancelled Then
    ' Tell user the process canceled here
  Else
    ' Tell user the process completed
    ' and you can use e.Result only here.
  End If
End If

If you want an object that accessible to all DoWork, ProgressChanged and RunWorkerCompleted, use like this:

Dim ThreadInfos as Dictionary(Of BackgroundWorker, YourObjectOrStruct)

You can easily access ThreadInfos(sender).Field anywhere you want.

CallMeLaNN
  • 8,328
  • 7
  • 59
  • 74
  • 1
    No. I put Throw New Exception('Test') in DoWork. Then RunWorkerCompleted triggered and I call e.Result before e.Error. This is wrong. Because of e.Error is not null (or Nothing), we cannot call e.Result. Calling e.Result when e.Error IsNot Nothing OR e.Cancelled = True will throw an Unhandled Exception in the RunWorkerCompleted. So check for e.Error and e.Cancelled before calling e.Result. – CallMeLaNN Apr 15 '10 at 16:24
  • Trying to access E.Result in my completed eventhandler was throwing a TargetInvocationException when my DoWork errored out. I now check if error is null or if it cancelled before accessing result, and my code is fixed! I'm telling you this because your explanation helped me fix my production code. Thanks so much! – Jeff LaFay Oct 20 '10 at 18:30
  • @CallMeLaNN Excellent info. Was doing the same mistake. – Soham Dasgupta Aug 04 '12 at 06:42
  • 1
    @SohamDasgupta Since TPL Introduced, I never use BackgroundWorker or create my own Thread anymore. It really nice to do inline code by playing with `Task` object. It makes my code more clean and let .NET manage the threads. Just a bit harder to make it thread-safe to report a progress or completed but once you got it, it really2 nice... – CallMeLaNN Aug 09 '12 at 01:57
  • Wow, this helped me a lot. Exactly the same happened to me. Thanks. – derFunk Mar 13 '14 at 14:14
11

By default it will be caught and stored by the BackgroundWorker. From MSDN:

If the operation raises an exception that your code does not handle, the BackgroundWorker catches the exception and passes it into the RunWorkerCompleted event handler, where it is exposed as the Error property of System.ComponentModel.RunWorkerCompletedEventArgs. If you are running under the Visual Studio debugger, the debugger will break at the point in the DoWork event handler where the unhandled exception was raised.

Stu Mackellar
  • 11,510
  • 1
  • 38
  • 59
4

As it was already noted:

If the operation raises an exception that your code does not handle, the BackgroundWorker catches the exception and passes it into the RunWorkerCompleted event handler, where it is exposed as the Error property of System.ComponentModel.RunWorkerCompletedEventArgs.

This is important whenever you're interacting with the original thread. For example if you want the result of your exception to be written in some kind of a label on your form, that's when you mustn't catch the exception in the DoWork of the BackgroundWorker, but instead handle the e.Error from RunWorkerCompletedEventArgs.

If you analyze the BackgroundWorker code with reflector you can see its all handled pretty straightforward: Your DoWork gets executed in a try-catch block, and the exception is just passed to RunWorkerCompleted. Which is the reason why I disagree with the 'preferred' method of always catching all your exceptions in the DoWork event.

In short, to answer the original question:

Yes - you can count on your RunWorkerCompleted to always be fired.

Use e.Error from RunWorkerCompleted to check for exceptions in the other thread.

Vedran
  • 10,369
  • 5
  • 50
  • 57
4

This will work only without the debugger attached, when run from Visual Studio, the debugger will catch the unhanded exception in DoWork method and will break the execution, however you may click continue and RunWorkerCompleted will be reached and you'll be able to read exception via the e.Error field.

TzOk
  • 51
  • 1