How can I wait on a BackgroundWorker's completion?
I would like to use the BackgroundWorker
to perform a database transaction from a GUI.
How can I command the BackgroundWorker
to perform the开发者_如何学运维 work and then WAIT for the worker to complete while keeping the GUI responsive?
Do I have to use DoEvents
for this purpose, or is there another way?
Asking "How can I command the BackgroundWorker
to perform the work and then WAIT for the worker to complete while keeping the GUI responsive?" is really asking "How do I use the BackgroundWorker
?". That's what BackgroundWorker
does.
When you write a background task, you're basically breaking up a method into four pieces:
- Getting ready to run the task.
- The task itself.
- Reporting progress to the UI while the task is running.
- Cleaning things up when the task is done.
So you're going to need to write four methods. The first is a method that creates a BackgroundWorker
, adds event handlers to its DoWork
, ProgressChanged
, and RunWorkerCompleted
events, puts the UI into whatever state it needs to be in while the task is running, and calls RunWorkerAsync
to start the task.
The other three are those three event handlers. DoWork
is a DoWorkEventHandler
that does the work, and that calls ReportProgress
whenever it needs to report its progress to the UI. ProgressChanged
is a ProgressChangedEventHandler
that actually updates the UI when ReportProgress
gets called. And RunWorkerCompleted
is a RunWorkerCompletedEventHandler
that tells the UI that the job is done.
That's all there is to it.
Well, not quite all. First, you have to make sure that you check and handle the Error
property in your completion handler. If you don't do this, you will have no way of knowing that your do-work method threw an exception, since it's not happening in the UI thread and thus doesn't throw an exception that you can see. (It'll show up in the Output window, if you're looking for it.)
Second, you have to make sure that the DoWorkEventHandler
doesn't touch anything in the UI. This can be tricky if you're using the MVVM pattern and you haven't planned for this contingency, because through the magic of data binding you probably have things in your model that update the views that the UI is bound to, which means that manipulating the model in your do-work method is manipulating the UI. Assuming that you're implementing INotifyPropertyChanged
, a good way to avoid trouble here is to build a mechanism whereby your views don't raise PropertyChanged
events while a background task is running.
You also need to figure out what pieces of the UI should be disabled while the task is running. This depends on your UI. The answer might be "all of it," and it might not. One of the things that makes using a modal form for your progress indicator attractive is that it frees you up from having to figure this out, since your entire UI is disabled while the modal form is open. If you're disabling controls or turning off property-change notifications in the launch method, you'll need to turn things back on again in the completion method.
And remember: any updates that the active parts of your UI make to your data model are a potential source of cross-thread data access errors. If your UI and the background thread ever update the same object, bad things happen - they're particularly bad if you haven't handled the Error
property in your completion handler, and the cross-thread exception kills the background task without your knowing it. If it sounds like I'm dwelling on this, it's because I've lost a lot of hours of my life to doing this particular thing wrong.
I usually wait for RunWorkerCompleted in separate modal form with progress bar. This forces end user to wait for finishing your operation, but GUI is responsive in a way that you can move forms, etc.
Better solution for end user will be to render some progress in status bar for example and allow him/her to perform only certain operations which can't break your program logic.
Is this what you want?
You use:
Application.DoEvents();
but you should read this link for a better way to update the UI from a backgroundworker thread
Basically, the code is like this:
// The declaration of the textbox.
private TextBox m_TextBox;
// Updates the textbox text.
private void UpdateText(string text)
{
// Set the textbox text.
m_TextBox.Text = text;
}
public delegate void UpdateTextCallback(string text);
Then, in your thread, you can call the Invoke method on m_TextBox, passing the delegate to call, as well as the parameters.
m_TextBox.Invoke(new UpdateTextCallback(this.UpdateText),
new object[]{”Text generated on non-UI thread.”});
Read this link also, for more information
You can call RunWorkerAsync like so:
this.backgroundWorker1.RunWorkerAsync();
And then you'd need to use the RunWorkerCompleted event.
Here's an example from MSDN :
private void backgroundWorker1_RunWorkerCompleted(
object sender, RunWorkerCompletedEventArgs e)
{
// First, handle the case where an exception was thrown.
if (e.Error != null)
{
MessageBox.Show(e.Error.Message);
}
else if (e.Cancelled)
{
// Next, handle the case where the user canceled
// the operation.
// Note that due to a race condition in
// the DoWork event handler, the Cancelled
// flag may not have been set, even though
// CancelAsync was called.
resultLabel.Text = "Canceled";
}
else
{
// Finally, handle the case where the operation
// succeeded.
resultLabel.Text = e.Result.ToString();
}
// Enable the UpDown control.
this.numericUpDown1.Enabled = true;
// Enable the Start button.
startAsyncButton.Enabled = true;
// Disable the Cancel button.
cancelAsyncButton.Enabled = false;
}
And that's it. I don't think there's any need to call DoEvents. The UI should still be responsive while the BackgroundWorker is running.
精彩评论