Why can't an exception be thrown in the BackgroundWorker RunWorkerCompleted event

I have looked at some posts on this topic that cover how to get around these problems, but I cannot fully understand Why is this odd behavior?

Short version :

Why is there Exception

being thrown inside an event RunWorkerCompleted

that doesn't get caught in the calling code?

Detailed version :

  • I have BackgroundWorker

    (now BGW).
  • BGW event DoWork

    callsException

  • The BGW event RunWorkerCompleted

    catches Exception

    , logs and does some cleanup work.
  • Once cleared, the event RunWorkerCompleted

    restarts Exception

    .

If the event RunWorkerCompleted

runs on the main thread, doesn't that mean that the calling code (also on the main thread) should catch this exception?

Some code to reinforce the concept ...

private void SomeMethod()
{
    BackgroundWorker bgw = new BackgroundWorker();
    bgw.DoWork += bgw_DoWork;
    bgw.RunWorkerCompleted += bgw_RunWorkerCompleted;
    bgw.RunWorkerAsync();
}

private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
    throw new Exception("Oops");
}

private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if(e.Error != null)
    {
        // Log exception & cleanup code here...
        throw e.Error; // Always unhandled :(
    }
}

      

I would imagine that a call SomeMethod

like this would catch Exception

and show the Message Box, but BGW will not behave as I expected and Exception

always remains unhandled ...

try
{
    SomeMethod();
}
catch (Exception)
{
    MessageBox.Show("Handled exception");
}

      

+3


source to share


2 answers


... misses the calling code?

"Calling code" is an important detail in your question, which exactly is your event handler called RunWorkerCompleted? You know this is not your code, you have never written one that explicitly calls an event handler. All you did was sign up for the event. This way you know there is a problem looming in there, since this is not your code, you cannot write try / catch to catch this exception and handle it.

I highly recommend that you just take a look, set a breakpoint on the event handler, and when it breaks, watch the Call Stack debug window to see how it got there. Keep in mind that this will be .NET Framework code, you need to disable the Just My Code debugger option so you can see everything.

The unusual cases first in a console application or service is SychronizationContext.Post () that called the event handler. The code runs on an arbitrary threadpool and there is no catch statement to catch this exception. Your application will always be completed.

A common case is Winforms, the last expression you see in the Call Stack window that has anything to do with your code is a call to Application.Run () in your Main () method. Your event handler was called by a call to Control.BeginInvoke (), you can't see it, but the way your event handler code ended up on the UI thread. Winforms has a catch backstop statement in its Run () method that catches and this will raise the Application.ThreadException event. It is only active when you are not using the debugger. Unless you have otherwise signed your own event handler, the default handler displays the ThreadExceptionDialog dialog box, which gives the user the option to click Continue or End. Not a great idea to ever let her go this farthere is no decent way for your user to choose the right one other than trial and error. Beware of the role the debugger plays, it will work differently without one and the ThreadException event will be thrown directly.



The next common case is WPF, it is very similar to Winforms, and your event handler was invoked by a call to Dispatcher.BeginInvoke (). It also has a catch statement in its dispatch loop. And similarly, this raises the DispatcherUnhandledException event. It doesn't have a default handler for an event like Winforms, if you are not subscribed the application will terminate.

Thus, in general, the somewhat inevitable result is that your application will terminate when the exception is re-raised. There's simply no magic godmother out there who wants to deal with a problem you didn't want to deal with. Handling exceptions like this is generally quite questionable, you have little idea what actually went wrong in the DoWork event handler. It is that he could not handle this exception, otherwise he would have caught it. The likelihood that you can do it later, and do it right, quickly diminishes, and later on the catch is removed from the statement that was thrown.

All you really know about the RunWorkerCompleted event handler is "it doesn't work". Handling such exceptions is risky, you don't know how much of your program state has been changed by the error code. There is a great advantage, however, the code you enter into DoWork is by its very nature very loosely coupled. Very important, tightly coupled code that runs on a worker thread is almost impossible to write, you can rarely get the right lock.

In practice, you are performing one operation that does not affect any state at all. Similar to a dbase query that populates a list with the query results. You use the e.Result property in your event handler to apply the result of the background operation. So no real harm, when it didn't work, you just didn't get the result. You must inform the user about this, because he did not receive the expected result. And often the user has to call someone to fix the problem, hopefully you are not the IT staff fixing the underlying problem. MesssageBox.Show () does the job.

+3


source


I figured this out while proving my own question. I'll share anyway, hopefully helping someone else. The answer lies somewhere between the method RunWorkerAsync()

and my stupid delusion.

RunWorkerAsync()

(as it clearly states) is an asynchronous method, so it doesn't block or wait . This means that the main thread can continue and therefore exit the try / catch even though BGW DoWork

might be busy.



When BGW DoWork

finally throws Exception

, the event is RunWorkerCompleted

no longer within the scope of the call / catch code.

It would actually be pointless to catch it in the calling code. Preventing it from being captured by the caller would result in an unstable race condition where it would sometimes get caught and in other cases it would be too late and unhandled.

+2


source







All Articles