views:

203

answers:

2

I'm familiar with the following:

"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."

However, I've encountered a weird glitch.

In my component, there's an instance of BackgroundWorker.

Even though it's not running in the debugger, the exception remains unhandled by the worker.

Even simplified code produces an unhandled exception (and RunWorkerCompleted doesn't fire):

Throw New ArgumentException("Test")

The main thing is the code of RunWorkerComplete:

RaiseEvent UpdateComplete(Me, New AsyncCompletedEventArgs(e.Error, e.Cancelled, e.Result))

I need the component to expose the worker exception through a public event.

If I remove the RaiseEvent call, the exception becomes handled by the worker, and accessible through e.Error.

Apparently, raising an event causes the worker to miss the exception. How can that be?

Full Code:

Component:

Private Sub workerDownloader_RunWorkerCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)

        RaiseEvent UpdateComplete(Me, New AsyncCompletedEventArgs(e.Error, e.Cancelled, e.Result))

End Sub

Private Sub workerDownloader_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
        Throw New ArgumentException("Test")
End Sub

Host app (WinForms):

Private Sub Connector1_UpdateComplete(ByVal sender As System.Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs) Handles Connector1.UpdateComplete
        If e.Error IsNot Nothing Then MessageBox.Show(e.Error.ToString)
End Sub
+2  A: 

It could be that your e.Result is throwing its own exception.

From the MSDN documentation on RunWorkerCompletedEventArgs:

Your RunWorkerCompleted event handler should always check the Error and Cancelled properties before accessing the Result property. If an exception was raised or if the operation was canceled, accessing the Result property raises an exception.

Specifically, it will raise a TargetInvocationException.

In the code you've posted, you are constructing a new AsyncCompletedEventArgs object from the argument passed through your RunWorkerCompleted event. I am not sure what your rationale for this is, but it looks to me like an unnecessary step since RunWorkerCompletedEventArgs inherits from AsyncCompletedEventArgs -- and so you can just pass your e (a RunWorkerCompletedEventArgs object) to your UpdateComplete event:

Private Sub workerDownloader_RunWorkerCompleted(ByVal sender As Object, ByVal e As RunWorkerCompletedEventArgs)
        RaiseEvent UpdateComplete(Me, e)
End Sub

Now, here's why this might fix your problem (and why the MSDN link I posted is relevant): the way you're currently doing it, you are accessing e.Result in your call to the constructor for AsyncCompletedEventArgs. By accessing the property there, before first checking e.Error, you are creating a scenario where an exception is thrown in the process of evaluating the parameters to pass to UpdateComplete. Because of this exception, your RaiseEvent line cannot finish what it's doing; hence, your UpdateComplete event is not being raised.

Dan Tao
That's not the case, as the unhandled exception is the one I thrown (ArgumentException - "Test"). I use "If e.Error IsNot Nothing Then MessageBox.Show(e.Error.ToString)"Any other suggestions?
Sphynx
@Sphynx, when you create a `AsyncCompletedEventArgs` you are accessing the `e.Result` without checking if there was an error or if the worker was canceled.
João Angelo
+1  A: 

Update based on your updates to the question... I've mocked this up in C# code and it looks like it's a (cross thread?) issue trying to pass the Result from the event args to the AsyncCompletedEventArgs.

Passing the error and the cancel works, but when I try to pass the result object, it dies. Do you need the result object in the event?

UpdateComplete(this, new AsyncCompletedEventArgs(e.Error, e.Cancelled, null));
Jim Leonardo
I don't need a result this time, and that works, thanks. But it's a bit weird anyways, isn't it?
Sphynx