问题
I am using a BackgroundWorker in my winforms app to perform a long running task that occurs in another class (performing database operations). Since all the work is done in another class the cancellation is not as simple. I am using an event in the other class (GenerateStats
) to update the progress as the background operation completes. I want to do a similar thing with cancelling the operation. I can't just call cancellationPending
in the DoWork function because the method would never see it until it finishes and that defeats the purpose. I want the cancellation functionality without having the pass the BackgroundWorker to generateForSubject()
. Is there anyway that this can support cancellation from the generateForSubject()
method in GenerateStats
class. This is the instantiation of the class that the operation performs in:
GenerateStats genStats = new GenerateStats();
This is my DoWork function, which calls the ReportProgress
method anytime the event in the other class gets called. It also calls the method from the other class generateForSubject()
that performs the operations.
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
genStats.ProgressChanged += (s, pe) => worker.ReportProgress(pe.ProgressPercentage, pe.UserState);
genStats.generateForSubject();
}
This is the button click event handler that should initiate the cancellation and run CancelAsync()
private void btnStop_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy)
{
backgroundWorker.CancelAsync();
}
}
This is my separate class that performs the operation, along with creating the ProgressChanged event handler so my class can updated the form with info on progress. It would be awesome if cancellation can perform similarly.
public event ProgressChangedEventHandler ProgressChanged;
protected virtual void OnProgressChanged(int progress, string message)
{
if (ProgressChanged != null)
{
ProgressChanged(this, new ProgressChangedEventArgs(progress, message));
}
}
public void generateForSubject()
{
//Perform db operation not important, but it takes time
OnProgressChanged(33, "Finished 1st set of stats");
//I hope to check for cancellation here
//Perform db operation not important, but it takes time
OnProgressChanged(66, "Finished 2nd set of stats");
//I hope to check for cancellation here
//Perform db operation not important, but it takes time
OnProgressChanged(99, "Finished 3rd set of stats");
//I hope to check for cancellation here
}
Just to clarify if there is any uncertainty as to what I am asking, is there any way for me to support cancellation of my backgroundWorker in the other class without passing the backgroundWorker to the method. If there is absolutely no way and I have to, then I will pass the backgroundWorker
回答1:
It would be helpful if you could be more specific about your reluctance to pass the BackgroundWorker
instance. Knowing why that's a design requirement could help produce a better answer to your question.
That said, you can apply the same philosophy you did for the ProgressChanged
event and delegate the cancellation check as well. For example:
class GenerateStats
{
public event EventHandler<CancelEventArgs> CheckCancel;
private bool OnCheckCancel()
{
EventHandler<CancelEventArgs> handler = CheckCancel;
if (handler != null)
{
CancelEventArgs e = new CancelEventArgs();
handler(this, e);
return e.Cancel;
}
return false;
}
public void generateForSubject()
{
//Perform db operation not important, but it takes time
OnProgressChanged(33, "Finished 1st set of stats");
if (OnCheckCancel())
{
// Or other cancellation logic here
return;
}
//Perform db operation not important, but it takes time
OnProgressChanged(66, "Finished 2nd set of stats");
if (OnCheckCancel())
{
// Or other cancellation logic here
return;
}
//Perform db operation not important, but it takes time
OnProgressChanged(99, "Finished 3rd set of stats");
if (OnCheckCancel())
{
// Or other cancellation logic here
return;
}
}
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
genStats.ProgressChanged += (s, pe) => worker.ReportProgress(pe.ProgressPercentage, pe.UserState);
genStats.CheckCancel += (sender1, e1) => e1.Cancel = worker.CancellationPending;
genStats.generateForSubject();
}
This allows the GenerateStats
class to check for pending cancellation without having direct access to the BackgroundWorker
instance, just as the ProgressChanged
event allows it to report progress via the BackgroundWorker
without direct access to the BackgroundWorker
.
回答2:
You method should periodically check for CancellationPending property to see if the request was made for cancellation. And then take a decision to cancel the operation. See MSDN link for this.
From MSDN:
Be aware that your code in the DoWork event handler may finish its work as a cancellation request is being made, and your polling loop may miss CancellationPending being set to true. In this case, the Cancelled flag of System.ComponentModel.RunWorkerCompletedEventArgs in your RunWorkerCompleted event handler will not be set to true, even though a cancellation request was made. This situation is called a race condition and is a common concern in multithreaded programming. For more information about multithreading design issues, see Managed Threading Best Practices.
EDIT:
If you do not wish to pass the backgroundworker to the generateForSubject method. Set a property in the class when CancelAsync is received. And check the value of this property from generateForSubject method before each operation.
来源:https://stackoverflow.com/questions/27097968/backgroundworker-cancellation