views:

132

answers:

6

I want to run an operation on a background thread. When it has completed I want to check for any errors that occurred and re-throw them on my original thread.

I am using a backgroundworker. Throwing an exception in the RunWorkerCompleted event handler results in an unhandled exception - this makes sense if the eventhandler is running on the background thread. If I had a winform control I could call Invoke or BeginInvoke but I do not have a winform control in this object, although it is a winform project.

How can I re-throw an exception that occurred in the backgroundworker?

private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Error != null)
        {              
            // I want to throw an exception here, without causing an unhandled exception and without being able to call Invoke or BeginInvoke on a WinForm control. 
        }
        else if (e.Cancelled)
        {
           // Do something useful
        }
        else
        {
            if (e.Result != null)
            {
               // Do something with the result
            }
        }
    }

I would have assumed that the RunWorkerCompleted event handler would be running on the original calling thread. Perhaps the backgroundworker is not what I need in this case.

+2  A: 

It's not possible to inject code into another running thread. Not even the operating system can do this.

Control.BeginInvoke works by putting the delegate reference in a queue and then using PostMessage to post a user-message into the UI thread's message queue. The Application.Run message loop looks for this message and when it finds it pops the delegate off the queue and executes it.

The point is that there is no other way to do what you need without your main thread being coded to look for a some kind of signal (or message) from the other thread.

Added

You stated that this is a WinForm application but you do not have a Control to use BeginInvoke with.

Edit: I suggested a lazy-load without thinking it through. The Control might end up getting created on the wrong thread.

Pre-create a Control prior to Application.Run that lives for the lifetime of the app. You can use this to BeginInvoke from.

Edit #3

So then I try this to make certain it works and of course it doesn't. You can't simply create a generic Control, it must have an HWND handle. Simple fix: create it like this:

invokerControl = new Control();
invokerControl.CreateControl();

That will allow you to BeginInvoke from it, even if there are no open Form objects to invoke from.

Tergiver
+1  A: 

Don't throw an exception.

Raise an event in the background worker which your main application thread subscribes to and then handle the error there - by throwing an exception if necessary.

ChrisF
The event will be fired on the background worker thread. That doesn't help get code executed on the thread that started the background worker.
Tergiver
@Tergiver - you subscribe to the event on the thread that started the background worker and then throw the exception there.
ChrisF
It doesn't matter where you subscribe to an event, the event handlers are all executed on the thread that fires the event.
Tergiver
@Tergiver - *of course* - not thinking straight. Though I can't delete this answer now as it's been accepted.
ChrisF
Yeah, Tergiver is right. Flip your answer to CW if you get downvoted to limit the damage.
Hans Passant
@Hans - good idea, don't know why I didn't think of that.
ChrisF
I accepted this answer because if I fire an event from the RunWorkerCompleted event handler and then subscribe to that event further up the layers of my app I can respond to it as I need to.
Peter Kelly
+1  A: 

You can check from other side. I mean - place timer (that will run in same main thread as form) on your form, and once per second - check some Exception field on your form (with lock()), and also some object field to detect that operation is completed. And then from bgw_RunWorkerCompleted wrap code with try...catch, and on catch (again with lock()) set Exception field of form to caught exception. But why not use Invoke or BeginInvoke?

Zakus
+1  A: 

If you didn't create the BGW instance on the UI thread then its RunWorkerCompleted event is going to run on an arbitrary threadpool thread. Any exception you throw on that thread is uncatchable and will terminate your app with a last gasp through AppDomain.UnhandledException.

In this case, there just isn't much use for BGW anymore. It is only nice to ensure that its events run on the UI thread. You might as well use MethodInvoker.BeginInvoke(). You'll need to think this through a bit and decide exactly what you're going to do when a bit of code off on some worker thread fails to do its job. Dealing with such a mishap is generally not possible and letting the program crash and burn is the right thing to do.

If you do want some kind of way to notify the user and try to keep the program stumbling along then you really ought to create the BGW instance on the UI thread. And use, say, MessageBox.Show() in the RunWorkerCompleted event handler. Be sure to recover your program state when you do this, you almost certainly need a catch clause in DoWork() to clean up the shrapnel.

Hans Passant
Thanks Hans. This is a system try app with no forms. The user chooses an item from a contextmenu and the app runs off and does some long-running process. The icon was not responding (i.e. no context menu showing when clicked) until I started using the BGW. So my problem seems to be solved except for re-throwing exceptions from the completed event - I want to show a BalloonTip on the icon if something has gone wrong on the long running process. However, I have since noticed that by firing an event from the runworkercompleted event I can do this - no need to throw exception.
Peter Kelly
Yuck, this doesn't sound healthy. Mention these things in your question next time. Good luck with it.
Hans Passant
Why does that not sound healthy? System tray app, background process, reporting errors.
Peter Kelly
Evidence that you're not pumping a message loop. Threading Hell is usually the result.
Hans Passant
mmmm...right. Okay, sounds like I need to do more reading on this and get some code reviews to see if there is a better way to do what I'm trying to do...
Peter Kelly
Well, ask a question about it. Not getting the context menu is the key problem, ask why.
Hans Passant
Okay, thanks Hans. I'll do that.
Peter Kelly
A: 

Just handle the RunWorkerCompleted event. This event is synchronized for you

    BackgroundWorker bgw;

    private void button1_Click(object sender, EventArgs e)
    {
        bgw = new BackgroundWorker();
        bgw.DoWork +=new DoWorkEventHandler(bgw_DoWork);
        bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
        bgw.RunWorkerAsync(bgw);
    }

    void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Error != null)
            textBox1.Text = e.Error.Message;
    }

    void bgw_DoWork(object sender, DoWorkEventArgs e)
    {
        throw new NotImplementedException();
    }
jyoung
I am handling the completed event - the problem is I want to throw the exception from there but it is being unhandled because it is not being thrown from the main thread. The background worker is not being used on a WinForm so the textbox1.Text any other controls are not relevant in this case.
Peter Kelly
A: 

If by chance you're using 4.0 you can switch to using a Task instead which will do what you want. See this example

Conrad Frix
unfortunately not, targeting 2.0 framework I'm afraid.
Peter Kelly