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 except开发者_如何学编程ion 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.
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.
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?
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.
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.
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();
}
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
精彩评论