Reputation: 4661
I have a window with a calendar and datagrid.
When the user selects a new date in the calendar I want to query the database for calls made on that date.
public HistoryDialog()
{
InitializeComponent();
worker = new BackgroundWorker();
worker.WorkerSupportsCancellation = true;
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
HistoryGrid.SelectionChanged += new SelectionChangedEventHandler(HistoryGrid_SelectionChanged);
}
void calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
startDate = calendar.SelectedDates.OrderBy(x => x.Date).FirstOrDefault();
endDate = calendar.SelectedDates.OrderByDescending(x => x.Date).FirstOrDefault();
if (endDate != startDate)
SelectedDateTextBlock.Text = String.Format("{0:d MMMM}", startDate) + " - " + String.Format("{0:d MMMM}", endDate);
else
SelectedDateTextBlock.Text = String.Format("{0:d MMMM}", startDate);
SearchInDatabase();
}
private void SearchInDatabase()
{
if (worker.IsBusy)
worker.CancelAsync();
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
return;
}
var CallLog = ct.GetCalllogs(startDate, endDate, userID); // Database query
e.Result = CallLog;
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
IList CallLog = e.Result as IList;
foreach (CalllogInfo callInfo in CallLog)
{
chvm.CallHistoryList.Add(callInfo);
}
}
But when the user selects a new date while the backgroundworker is still running my program crashes.
How can I stop a running background worker and start a new one ?
Upvotes: 3
Views: 10077
Reputation: 23300
Disallowing selections while the worker is doing work would remove the issue without the need to tackle it:
void calendar_SelectedDatesChanged(object sender, SelectionChangedEventArgs e)
{
// .. cut ..
// disable the calendar and go on
calendar.IsEnabled = false;
SearchInDatabase();
}
private void SearchInDatabase()
{
// No longer needed: selection processing is now "atomic"
// if (worker.IsBusy) worker.CancelAsync();
worker.RunWorkerAsync();
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// .. cut ..
calendar.IsEnabled = true;
}
Or you could bind worker.IsBusy
to calendar.IsEnabled
to have it handled "automagically" (while still not having to worry about concurrent executions of the worker method).
Upvotes: 0
Reputation: 22456
In addition to the other answers, I want to add the following. The following block of code is responsible for raising the error:
private void SearchInDatabase()
{
if (worker.IsBusy)
worker.CancelAsync();
worker.RunWorkerAsync();
}
As you call CancelAsync, execution of your code is continued immediately without waiting until the BackgroundWorker is really stopped. This is the reason why RunWorkerAsync is called while the BackgroundWorker is still running resulting in the error you describe. You should change your code as follows:
private void SearchInDatabase()
{
if (worker.IsBusy)
worker.CancelAsync();
while(worker.IsBusy)
System.Threading.Thread.Sleep(100);
worker.RunWorkerAsync();
}
This assures that the BackgroundWorker finishes its work before starting a new one. In addition, you need to enhance your DoWork-method to check the CancellationPending property more often in order to really stop the BackgroundWorker soon after a cancellation request.
Upvotes: 1
Reputation: 12683
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.
Upvotes: 4
Reputation: 31
Your problem is coming from the cancellation.
When you are in ct.GetCalllogs
your code is blocked and doesn't support cancellation.
In ct.GetCalllogs
you should verify that the backgroundworker isn't in cancel state and then start again with the new value.
Here is an element of response how to stop backgroundworker correctly
Upvotes: 0