Stop running backgroundworker and start new one. - c#

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 ?

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.

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.

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

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).

Related

C# BackgroundProcess: is it safe to use .CancelAsync before .RunworkerAsync?

When the user tries to double click button, the backgroundWorker will initiate twice.
is this a good workaround?
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Some processing here
Thread.Sleep(1000);
button1.Invoke((MethodInvoker)delegate { button1.Enabled= true; });
}
// Run backgroundProcess
private void button1_Click(object sender, EventArgs e)
{
this.button1.Enabled = false;
backgroundWorker1.CancelAsync();
backgroundWorker1.RunWorkerAsync();
}
You can use backgroundWorker1_RunWorkerCompleted event to enable button1. instead for enabling then in backgroundWorker1_DoWork
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.button1.Enabled = true;
//rest of operations
}
What is wrong with your code:
You ware enabling the button in _DoWork that is meaning less since backgroundWorker1.RunWorkerAssync(); will call _DoWork() there the button enables and hence the second click also get executed.
You have to take care of three basic things when you are going to use BackgrounWorker.
DoWork: You can handle execution of your business logic and cancellation of your work if required.
RunWorkerCompleted: You can handle post execution task. Here you want to enable button, so you should write appropriate code into RunWorkerCompleted Event.
Handle the WorkerSupportsCancellation property if you want to cancel backgroundworker process at any point of execution.
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
button1.Enabled = false;
backgroundWorker1.RunWorkerAsync();
}
void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Invoke((MethodInvoker)delegate { button1.Enabled = true; });
backgroundWorker1.DoWork -= backgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted -= backgroundWorker1_RunWorkerCompleted;
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
AppendTextBox( "Hit: " + i.ToString() + Environment.NewLine);
if (backgroundWorker1.CancellationPending)
{
break;
}
}
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
}
}
public void AppendTextBox(string value)
{
if (InvokeRequired)
{
this.Invoke(new Action<string>(AppendTextBox), new object[] { value });
return;
}
textBox1.Text += value;
}
private void button2_Click(object sender, EventArgs e)
{
if (backgroundWorker1.IsBusy)
backgroundWorker1.CancelAsync();
}
If the worker is actually running, then almost certainly not.
Cancellation is cooperative. Calling CancelAsync just sets the CancellationPending flag to true. It is up to the background worker to periodically check this flag and respond to cancellation.
The chances that a running worker has gotten to an appropriate point to check that flag, checked it, and responded, all within the gap of time between these two lines of code:
backgroundWorker1.CancelAsync();
backgroundWorker1.RunWorkerAsync();
Is extremely slim. It's more likely (if the worker was running) that RunWorkerAsync will throw InvalidOperationException since the worker is still running.

Timer tick events does not fire in a backgroundworker do_work

I have a background worker which i am using to perform some task. Its working as expected. However, i have a timer that i want to add and make it start the bw and counting like 10 seconds after page load. I put my timer.Interval to 10000. the timer has a tick events as below
private DateTime dateETA;
private void TimerEventHandler(object sender, EventArgs e)
{
while (bw.CancellationPending ==false)
{
if (timerPro.Enabled == true)
{
dateETA = Convert.ToDateTime("1/1/0001 00:00:00");
dateETA = dateETA.AddMilliseconds(timerPro.Interval);
lblETA.Visible = true;
lblETA.Text = "Elapsed Time : " + Convert.ToString(dateETA.TimeOfDay);
// SetText("timer");
}
}
}
My background worker async is on the page contructor method and therefore run on load. just like below
if (bw.IsBusy != true)
{
this.btnPause.Enabled = true;
this.btnStop.Enabled = true;
btnStart.Enabled = false;
// timerPro.Start();
bw.RunWorkerAsync();
}
I wanted to start the timer together with my task therefore i put it before my bw.async . Then i realized the timer tick events does not fire when put before or within the dowork method of the background worker. I thought may be the bw thread is blocking the event from firing then i use an invoke method like below within the dowork in my attempt to start the timer or trigger the tick event of the timer.
this.Invoke((MethodInvoker)(() => { timerPro.Enabled = true; }));
It still does not fire. I am confused and any help or alternative would be appreciated.
I think you just want a running elapsed timer while the backgroundworker does its thing?
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private System.Diagnostics.Stopwatch SW = new System.Diagnostics.Stopwatch();
private void Form1_Load(object sender, EventArgs e)
{
timerPro.Interval = 1000;
timerPro.Tick +=new EventHandler(TimerEventHandler);
SW.Start();
timerPro.Start();
bw.RunWorkerAsync();
}
private void TimerEventHandler(object sender, EventArgs e)
{
lblETA.Visible = true;
TimeSpan TS = SW.Elapsed;
string elapsed = String.Format("{0}:{1}:{2}", TS.Hours.ToString("00"), TS.Minutes.ToString("00"), TS.Seconds.ToString("00"));
lblETA.Text = "Elapsed Time : " + elapsed;
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
// ... do some work ...
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
timerPro.Stop();
}
}

Run task asynchronously in C#

I have some process heavy tasks that run in my WinForms app. The problem is, while its running, it freeze the UI (UI main thread).
I haven't worked that much with threads and delegates in C# yet, and that's why I hope someone could help me to, how to handle those process heavy tasks, without freezing the UI, so the user don't think the app is crashing while waiting?
Eg. I have a call through my FrontController, that takes time:
_controller.testExportExcel(wrapper, saveDialog.FileName);
Since it's creating an Excel file. I won't the app to be responding on the UI while its working.
Another example of a process heavy task could be this:
private void dataGridView_liste_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
{
if (e.ListChangedType != ListChangedType.ItemDeleted)
{
foreach (DataGridViewRow r in dataGridView_liste.Rows)
{
DataGridViewCellStyle red = dataGridView_liste.DefaultCellStyle.Clone();
red.BackColor = Color.LightGreen;
if (r.Cells["News"].Value != null && (bool)r.Cells["News"].Value == true)
r.DefaultCellStyle = red;
}
}
}
Where the foreach loop takes time, and freeze the UI. An async thread running the process and automatically closing when its done, could be useful I think. But how does it work??
How about using a Task (if targetting .net 4)? This is considered as a replacement of the BackgroundWorker class since it supports nesting (parent/child tasks), task continuations, etc.
E.g.
private void dataGridView_liste_DataBindingComplete(object sender,
DataGridViewBindingCompleteEventArgs e)
{
Task t = Task.Factory.StartNew(() =>
{
// do your processing here - remember to call Invoke or BeginInvoke if
// calling a UI object.
});
t.ContinueWith((Success) =>
{
// callback when task is complete.
}, TaskContinuationOptions.NotOnFaulted);
t.ContinueWith((Fail) =>
{
//log the exception i.e.: Fail.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);
}
I answered a very similar question here
It boils down to using BackgroundWorker.
msdn provides an example:
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
namespace SL_BackgroundWorker_CS
{
public partial class Page : UserControl
{
private BackgroundWorker bw = new BackgroundWorker();
public Page()
{
InitializeComponent();
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
}
private void buttonStart_Click(object sender, RoutedEventArgs e)
{
if (bw.IsBusy != true)
{
bw.RunWorkerAsync();
}
}
private void buttonCancel_Click(object sender, RoutedEventArgs e)
{
if (bw.WorkerSupportsCancellation == true)
{
bw.CancelAsync();
}
}
private void bw_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));
}
}
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{
this.tbProgress.Text = "Canceled!";
}
else if (!(e.Error == null))
{
this.tbProgress.Text = ("Error: " + e.Error.Message);
}
else
{
this.tbProgress.Text = "Done!";
}
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.tbProgress.Text = (e.ProgressPercentage.ToString() + "%");
}
}
}
Everything that runs in the DoWork event handler is asynchronous.
Everything that runs in ProgessChanged/RunWorkCompleted's event handlers is on the UI thread.
For your first example, a call to _controller.testExportExcel(), a BackgroundWorker or Task Parallel Library call (i.e. Task.Factory.StartNew(...)) would be appropriate to satify your requirement of keeping the UI responsive. Plenty of examples floating around, including the other answers here.
For your second example, you will find you can't put this on a background thread since it appears to be code that manipulates the UI. Specifically, if the implementation of your BackgroundWorker's DoWork event handler, or the delegate you pass to Task.Factory.StartNew(), or the method for a plain old thread touch the UI, you are highly likely (/certain?) to get an exception stating "Cross-thread operation not valid".
The reason for this is covered in this question. But I'm more surprised actually this is slow enough that you want to make it asynchronous. There might be some simple ways to make this code more responsive - Control.SuspendLayout() and .ResumeLayout() springs to mind.

WinForm Multithreading. Use backgroundWorker or not?

I have a simple app which fires of a series of data intensive tasks. I'm not very experienced with WinForms and I was wondering the best way to do this without locking the interface. Can backgroundWorker be re-used, or is there another way to do this?
Thanks
BackgroundWorker is a thread that also includes notification synchronization. For example, if you wanted to update your UI when the scan completes, a regular Thread cannot access the UI objects (only the UI thread can do that); so, BackgroundWorker provides a Completed event handler that runs on the UI thread when the operation completes.
for more info see: Walkthrough: Multithreading with the BackgroundWorker Component (MSDN)
and a simple sample code:
var worker = new System.ComponentModel.BackgroundWorker();
worker.DoWork += (sender,e) => Thread.Sleep(60000);
worker.RunWorkerCompleted += (sender,e) => MessageBox.Show("Hello there!");
worker.RunWorkerAsync();
backgroundWorker can be used.
its benefit - it allows you to update a progress bar and interact with UI controls. (WorkerReportsProgress)
Also it has a cancellation mechanism. (WorkerSupportsCancellation)
You can use BackgroundWorker for such requirements. Below is a sample which updates a label status based on percentage task [long running] completion. Also, there is a sample business class which sets some value and the value is set back to UI via ProgressChanged handler. DoWork is the place where you write your long running task logic. Copy-Paste the code below after adding a label and backgroundworker component on a Winforms app & give it a shot. You may debug across various handler [RunWorkerCompleted, ProgressChanged, DoWork] and have a look at InitWorker method. Notice the cancellation feature too.
using System.ComponentModel;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form3 : Form
{
private BackgroundWorker _worker;
BusinessClass _biz = new BusinessClass();
public Form3()
{
InitializeComponent();
InitWorker();
}
private void InitWorker()
{
if (_worker != null)
{
_worker.Dispose();
}
_worker = new BackgroundWorker
{
WorkerReportsProgress = true,
WorkerSupportsCancellation = true
};
_worker.DoWork += DoWork;
_worker.RunWorkerCompleted += RunWorkerCompleted;
_worker.ProgressChanged += ProgressChanged;
_worker.RunWorkerAsync();
}
void DoWork(object sender, DoWorkEventArgs e)
{
int highestPercentageReached = 0;
if (_worker.CancellationPending)
{
e.Cancel = true;
}
else
{
double i = 0.0d;
int junk = 0;
for (i = 0; i <= 199990000; i++)
{
int result = _biz.MyFunction(junk);
junk++;
// Report progress as a percentage of the total task.
var percentComplete = (int)(i / 199990000 * 100);
if (percentComplete > highestPercentageReached)
{
highestPercentageReached = percentComplete;
// note I can pass the business class result also and display the same in the LABEL
_worker.ReportProgress(percentComplete, result);
_worker.CancelAsync();
}
}
}
}
void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
// Display some message to the user that task has been
// cancelled
}
else if (e.Error != null)
{
// Do something with the error
}
}
void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Text = string.Format("Result {0}: Percent {1}",e.UserState, e.ProgressPercentage);
}
}
public class BusinessClass
{
public int MyFunction(int input)
{
return input+10;
}
}
}
The background worker would be a good choice to start with
For more info look here
http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx

C#: how to do basic BackgroundWorkerThread

Let's say I have the following function in C#:
void ProcessResults()
{
using (FormProgress f = new FormProgress()) {
f.ProgressAmount = 10;
// I want to have the following line run in a BackgroundWorkerThread
RetrieveAndDisplayResults();
f.ProgressAmount = 100;
}
}
What would I need to do for the line RetrieveAndDisplayResults(); to be run in a BackgroundWorkerThread?
var f = new FormProgress()
f.ProgressAmount = 10;
var worker = new BackgroundWorker();
worker.DoWork += (o, e) => RetrieveAndDisplayResults();
worker.RunWorkerCompleted += (o, e) =>
{
f.ProgressAmount = 100;
f.Close();
}
worker.RunWorkerAsync();
If your method is updating the UI, you'll have to change it to return the results, and then display them in the worker's RunWorkerCompleted event.
You can also use the ProgressChanged event and ReportProgress method to have more granular progress updates.
Your current approach is not well suited for using a Thread (Bgw or otherwise).
The main problem is the 'waiting' part before setting progress=100. What should the this method be doing in that time?
You can reverse the logic, launch a Bgw and use the Progress and Completed events to Update resp. Close your form.
You will probably have to change your approach, but the code below should be able to give a scaffolding for a long-running task that updates the UI as it progresses:
private void LaunchWorker()
{
var worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(OnDoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(OnProgressChanged);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnRunWorkerCompleted);
worker.RunWorkerAsync();
}
void OnDoWork(object sender, DoWorkEventArgs e)
{
var worker = sender as BackgroundWorker;
while (aLongTime)
{
worker.ReportProgress(percentageDone, partialResults);
}
e.Result = results;
}
void OnProgressChanged(object sender, ProgressChangedEventArgs e)
{
var progress = e.ProgressPercentage;
var partialResults = e.UserState;
// Update UI based on progress
}
void OnRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
var results = e.Result;
// Do something with results
}

Categories