How to Stop Backgroundworker Correctly

How to stop BackgroundWorker correctly

CancelAsync doesn't actually abort your thread or anything like that. It sends a message to the worker thread that work should be cancelled via BackgroundWorker.CancellationPending. Your DoWork delegate that is being run in the background must periodically check this property and handle the cancellation itself.

The tricky part is that your DoWork delegate is probably blocking, meaning that the work you do on your DataSource must complete before you can do anything else (like check for CancellationPending). You may need to move your actual work to yet another async delegate (or maybe better yet, submit the work to the ThreadPool), and have your main worker thread poll until this inner worker thread triggers a wait state, OR it detects CancellationPending.

http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx

http://www.codeproject.com/KB/cpp/BackgroundWorker_Threads.aspx

Stopping background worker

Same answer as Marc Gravell but you don't seem to follow.

Are you setting e.cancel = true?

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;

for (int i = 1; i <= 10; i++)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
else
{
// Perform a time consuming operation and report progress.
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
}

Stop running backgroundworker and start new one.

Now I see you have set the WorkerSupportsCancellation property to true.

Now this doesnt actually cancel your BackgroundWorker it simply allows you to call the CancelAsync() method.

What you need to do is in your method processing periodically check to ensure the working is not pending cancellation from the CancellationPending property. As you check this property when you find it true you can set the Cancel property of the event arguments to true. This will then be available in your RunWorkerCompleted event. At this point (in your RunWorkerCompleted event handler) you can then restart the BackgroundWorker.

Here is an example using very basic background worker that supports cancellation and responds to the cancel request.

public MainWindow()
{
InitializeComponent();
this.DataContext = dataModel;

worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += (o, e) =>
{
//do a long running task
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(500);
if (worker.CancellationPending)
{
e.Cancel = true;
return;
}
}

};
worker.RunWorkerCompleted += (o, e) =>
{
if (e != null && e.Cancelled)
{
startTheWorker();
return;
}
//TODO: I AM DONE!
};
}

BackgroundWorker worker;

private void Button_Click(object sender, RoutedEventArgs e)
{
if (worker != null && worker.IsBusy)
worker.CancelAsync();
else if(worker != null && !worker.CancellationPending)
startTheWorker();
}

void startTheWorker()
{
if (worker == null)
throw new Exception("How come this is null?");
worker.RunWorkerAsync();
}

As you can see we are having to do all the work of actually cancelling the worker. Hope this helps.

How to cancel backgroundworker from some part of method's code

Don't catch the exception in the method. Let the caller handle it in BackgroundWorker's RunWorkerCompleted event.

See Background worker exception handling

Backgroundworker cancel the worker

I agree with the comments expressing surprise that the code even works, due to the apparent ordering problem of the call to RunWorkerAsync() vs when you actually subscribe to the DoWork event. Additionally, your use of DoEvents() is unwarranted and should be removed (as is the case with any use of DoEvents()).

I also note that your workers don't actually exit when you try to cancel them. You just skip the processing, but continue to loop on the rows. Without seeing the rest of the code, it's impossible to know what's going on, but it's possible that after you cancel, the CancellationPending property gets reset to false, allowing the loops to start doing things again.

The lack of a complete code example is a real impediment to understanding the full detail of what's going on.

That said, IMHO this does not seem to be a case where you actually need BackgroundWorker at all, not with the new async/await feature in C#. Given that network I/O is involved, my guess is that each call to sendDocumentsToMySQL() and sendArticlesToMySQL() can be executed individually in the thread pool without too much overhead (or may even be able to be written as async I/O methods…again, lacking detail as to their specific implementation prevents any specific advise in that respect). Given that, your code could probably be rewritten so that it looks more like this:

private CancellationTokenSource _cancelSource;

private void stopButton_Click(object sender, EventArgs e)
{
if (_cancelSource != null)
{
_cancelSource.Cancel();
}
}

private async void startButton_Click(object sender, EventArgs e)
{
using (CancellationTokenSource cancelSource = new CancellationTokenSource)
{
_cancelSource = cancelSource;

try
{
foreach (string[] conn in lines)
{
string connectionString = conn[0];

FbConnection fbConn = new FbConnection(connectionString);
fbConn.Open();

try
{
await getDocuments(fbConn, cancelSource.Token);
await getArticles(fbConn, cancelSource.Token);
}
catch (OperationCanceledException)
{
return;
}
finally
{
fbConn.Close();
}
}
}
finally
{
_cancelSource = null;
}
}
}

private async Task getDocuments(FbConnection fbConn, CancellationToken cancelToken)
{
DataTable dt = await Task.Run(() => getNewDocuments(fbConn));

for (int i = 0; i <= dt.Rows.Count - 1; i++)
{
cancelToken.ThrowIfCancellationRequested();

await Task.Run(() => sendDocumentsToMySQL((int)dt.Rows[i]["ID"]));
}
}

private async Task getArticles(FbConnection fbConn, CancellationToken cancelToken)
{
DataTable dt = await Task.Run(() => getNewArticles(fbConn));

for (int i = 0; i <= dt.Rows.Count - 1; i++)
{
cancelToken.ThrowIfCancellationRequested();

await Task.Run(() => sendArticlesToMySQL((int)dt.Rows[i]["ID"]));
}
}

How to stop BackgroundWorker by specific message in ReportProgress

The ReportProgress method is to... report progress. The only thing you can pass in is an percentage and the user state. That's all. You shouldn't use it to pass in an error.

Instead, the best thing you can do is to throw an exception. This will bubble up till the RunWorkerCompleted event handler, which has an event argument property named Error. Check that property in the event handler you write and you know what went wrong.

Proper way to cancel BackgroundWorker

Use the Result prorperty of the DoWorkEventArgs instance you get passed in DoWork:

void InsertIDsNamesAndAddWorker_DoWork(object sender, DoWorkEventArgs e)
{
if (columns == 5)
{
//do your normal processing
e.Result = true; // we're OK
}
else
{
//if requirements are not met then display error message
System.Windows.MessageBox.Show("There must be five columns in this
file", MessageBoxButton.OK, MessageBoxImage.Error);

e.Result = false; // something wrong
}
}

and then in RunWorkerCompleted check the Result value and handle accordingly.

void InsertIDsNamesAndAddWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// check if we're not cancelled or error-ed before checking the Result
result = (!e.Cancelled && e.Error == null)? (bool) e.Result: false; // what is the outcome

ProgressBarValue = 100;
if (result) {
StatusLable = "Done Processing.";
if (System.Windows.MessageBox.Show("Done Processing.", "Status", MessageBoxButton.OK, MessageBoxImage.Information) == MessageBoxResult.OK)
{
StatusLable = string.Empty;
ProgressBarValue = 0;
}
}
else
{
StatusLable = "Error in Excel sheet";
}
}

Notice that Result is of type object. You can put any instance of a type into it, even your own class, which might be needed if you want to return more fine grained details about what went wrong.



Related Topics



Leave a reply



Submit