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
How to Compare Two Rich Text Box Contents and Highlight the Characters That Are Changed
Executing Ssis 2012 Package That Has Script Components from External Application
Does .Net Provide an Easy Way Convert Bytes to Kb, Mb, Gb, etc.
Pass-Through Mouse Events to Parent Control
Why Switch for Enum Accepts Implicit Conversion to 0 But No for Any Other Integer
Read Connection String from Web.Config
Resharper Wpf Error: "Cannot Resolve Symbol "Myvariable" Due to Unknown Datacontext"
String = String + Int: What's Behind the Scenes
Why Does the Order of Alternatives Matter in Regex
Readonlycollection or Ienumerable for Exposing Member Collections
Getting Type T from Ienumerable<T>
Searching for File in Directories Recursively