简体   繁体   中英

Thread and BackgroundWorker

I have a WinForm C# app and I want to perform some calculations that may takes some second to complete. so in order to avoid threads issue and app freezing I have used BackgroundWorker . when I click the button at the first time, all things are quite well and Start button become invisible and cancel button turn visible and app has not freezing issue and progress bar changes successfully. but for the second time, when I click the button, for some seconds start button become invisible and cancel button also is invisible though it must be visible, and app go to freezing state and after that all things become good. i do not know why it works as I need just only at the first try. Here the code I use.

Note : I've added BackgroundWorker form toolbox to my form.

private void btnStart_Click(object sender, EventArgs e)
{
     btnStart.Visible = false;
     btnCancel.Visible = true;
     progressBar1.Value = 0;
     progressBar1.Visible = true;
     backGroundWorker.RunWorkerAsync();

}

private void backGroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
   Calculate(backGroundWorker, e);
}

private void backGroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    progressBar1.Value = e.ProgressPercentage;
}

private void backGroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
   btnStart.Visible = true;
   btnCancel.Visible = false;
   progressBar1.Visible = false;
}

void Calculate(BackgroundWorker instance, DoWorkEventArgs e)
{
 
  // Do Some Works
  instance.ReportProgress(counter);

}

You are almost finished. You only forgot to let your backgroundworker support cancelling. Furthermore: your backgroundworker shouldn't use anything from outside the backgroundworker. If the backgroundworker needs some data from the form to execute the task, pass this as a parameter when you start working.

A lot of the following can be done using the designer. If you want you can program this yourself. Don't forget to Dispose the backgroundworker when your form is disposed

BackgroundWorker backgroundWorkder = new BackgroundWorker
{
    WorkerReportsProgres = true,
    WorkerSupportsCancellation = true,
}

// ensure that backgroundWorker is disposed when the form is disposed:
this.Components.Add(backgroundWorker); 
// alternative: react on event from closed

backgroundWorker.DoWork += OnDoBackGroundWork;
backgroundWorker.ProgressChanged += OnProgressReported;
backgroundWorker.RunWorkerCompleted += OnBackgroundWorkCompleted;

The first event handler will be executed by the backgroundWorker. This event handler should not touch anything from the form.

The later two event handlers will be executed by the main thread. They can use items from the form.

private void StartBackgroundWork()
{
    // TODO: enable / disable buttons; show progress bar

    // if your backgroundWorker needs parameters:
    MyBackgroundParameters params = new MyBackgroundParameters()
    {
        Text = this.textBox1.Text,
        Value = this.comboBox1.SelectedIndex,
        ...
    };
    this.backgroundWorker.RunWorkerAsync(params);
}

private void CancelBackgroundwork()
{
    this.backgroundWorker.CancelAsync();
}

There is no need to check if the backgroundWorker is already finished. If so, cancelAsync won't do anything. Besides, if you would check IsBusy , then before you CancelAsync the backgroundWorker could become completed.

The event handlers:

private void OnDoBackgroundWork(object sender, DoWorkEventArgs e)
{
    // if there are several backgroundWorkers that could call this, you should check 
    // the sender, to determine what work should be done and which parameters are passed
    BackgroundWorkder backgroundWorker = (BackgroundWorker)sender;
    MyBackGroundParameters params = (MyBackGroundParameters) e.DoWorkEventArgs;

    // use these params to do your calculations.
    // regularly, check if cancellationpending
    bool calculationFinished = false;
    while (!calculationFinished && !backgroundWorker.CancellationPending)
    {
        // do a short part of the calculation
        progress = performNextCalculationStep();
        backgroundWorker.ReportProgress(...)
        calculationFinished = ...
    }

    // if here, either calculation finished, or cancelled:
    MyBackgroundResult backgroundResult;
    if (calculationFinished)
    {
        backgroundResult = new MyBackgroundResult
        {
            ... // fill with values if completed
        };
    }
    else
    {
        // cancelled
        backgroundResult = new MyBackgroundResult
        {
            ... // fill with values if cancelled
        };
    }
    e.Result = backgroundResult;
}

Here I have parameters to indicate in the result whether the calculations were completer, or whether they were cancelled. If you want, you can also give different objects back. The event handler should check the type of the object to see whether the calculations were cancelled or not.

private void backGroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    btnStart.Visible = true;
   btnCancel.Visible = false;
   progressBar1.Visible = false;

   MyBackgroundResult result = (MyBackgroundResult)e.Result;
   this.ProcessResult(result);
}

Don't forget to subscribe on form closing event, and check if the backgroundworker is still busy. Ask the operator if this work can be cancelled. If not, cancel closing, if can be cancelled, cancel the backgroundwork and wait until finished in the event form closed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM