Canceling Worker threads in winforms - c#

I've got two list boxes, one a master, the other a child. When the index changes on the master, the child listbox gets filled appropriately with records relating to the master. My problem is coming up when one master takes a long time to get all the records and before it has completed getting the records, a user clicks a different master that takes less time to fill. What happens is that eventually, the master that was taking longer fills in the child list box even though the user isn't on that master anymore.
I've been using BackgroundWorker threads to do the filling.
bw_LoadAll.DoWork += new DoWorkEventHandler(bg_LoadAllWork);
bw_LoadAll.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_LoadAllWorkCompleted);
bw_LoadAll.WorkerSupportsCancellation = true;
I subscribed to the SelectedIndexChanged event for the master and I set the canceled equal to true:
bw_LoadAll.CancelAsync();
Here is the code in the DoWork method:
List<object> s = Repository.Instance().LoadAll();
if (!bw_LoadAll.CancellationPending) {
e.Result = s;
} else {
e.Cancel = true;
}
But for some reason, the code for the worker completed keeps getting called. Here is the worker completed code:
if (!e.Cancelled) {
ddl.DataSource = e.Result;
ddl.DisplayMember = "QuickName";
ddl.ValueMember = "ID";
}
Is there something else I have to do to cancel this thread from returning?

Your method, bg_LoadAllWork, should be defined as:
void bg_LoadAllWork(object sender, DoWorkEventArgs e)
{
// Do your work here...
}
Inside of bg_LoadAllWork, you need to check for e.CancellationPending, and if it's true, set e.Cancel = true;
This last part is important - if you never set e.Cancel, then your "e.Cancelled" will never equal true. The call to CancelAsync doesn't actually cancel anything - it's more like "Request that the background work cancel itself" - you have to put the logic in place to cause the cancellation.

From CodeProject in your do_work function you need to check for CancellationPending on the worker thread then set the DoWorkEventArgs.Cancel variable to true.

It's been a while since I've used a BackgroundWorker, but if I memory serves, when you call bw_LoadAll.CancelAsync, there's no actual abortion of your LoadAllWork method unless LoadAllWork checks bw_LoadAll.CancelationPending.
Confirmed by http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.cancelasync.aspx : "CancelAsync submits a request to terminate the pending background operation and sets the CancellationPending property to true.
When you call CancelAsync, your worker method has an opportunity to stop its execution and exit. The worker code should periodically check the CancellationPending property to see if it has been set to true."
So, in your SelectedIndexChanged event handler, when you call bw_LoadAll.CancelAsync(), it's setting bw_LoadAll.CancelationPending to true, but it's not actually aborting the LoadAllWork method. The slow-loader is going to still finish.

Related

UI button running on separate thread?

my issue is the following:
1.I have an intensive method which updates GUI element (chart) in a while loop
2.I need to break out of this loop when a button is pressed.
3.Problem is the button event handler doesn't get executed until the while loop is finished.
4.I've tried running the method on separate thread but that is very problematic since it sets and reads many UI elements, pretty much I couldn't get that to work, so I'm hoping of there being a way to run just the stop button on a separate thread and update a global variable which let's me break out of the loop.
Any Idea how that can be accomplished?
private void playBack(int playTime, int runUntil)
{
var frameTime = new DateTime(); var frameTime_ = new DateTime();
bool fwd = true;
if (runUntil < playTime) fwd = false;
playTime = trPlay.Value;
playGoStop = true;
lbPlayTime.Text = (playTime * numDtStepSize.Value).ToString();
while (true) //trPlay.Maximum + 1)
{
frameTime = DateTime.UtcNow;
if ((frameTime - frameTime_).TotalMilliseconds > (double)(1000 / numFps.Value))
{
systemUpdate(playTime);
trPlay.Value = playTime;
trPlay.Update();
lbPlayTime.Update();
frameTime_ = frameTime;
if (fwd)
{
playTime++;
if (playTime > runUntil) break;
}
else
{
playTime--;
if (playTime < runUntil) break;
}
}
if (!playGoStop) break;
}
}
In your while loop, you can call Application.DoEvents(). It will fetch UI events from event queue, and process those events. Then, your button events will be processed.
You can search by the keyword Application.DoEvents() and C#. There are many topics about it.
UPDATE:
In your codes, it is a infinite while loop inside. I don't like to run a infinite in main thread. I will prefer to run it in a worker-thread. And send message to do UI updates in main-thread. Generally, UI events needs to be processed in the main-thread.
If the infinite while loop is already in the worker-thread, it should sleep about 5~10 ms per loop, in order to free CPU resources to process some events in the other threads.
You should look at binding your UI element (chart) data to a DependencyProperty. This allows you to run the intensive method on a non-UI thread, allowing your UI thread to be responsive to button clicks. While on the background thread, simply make Dispatcher.BeginInvoke() calls to update your DependencyProperty (as it can only be updated from the UI thread) and this will update your control bound to it.
As far as your button interrupt goes, a simple solution is to set a flag from your UI which is checked within each loop iteration. A more complex solution would be to run this intensive method in a task Task, giving it CancellationTokenSource, then cancel the source upon button click.

SQL Query inside BackgroundWorker

I am looking for implementation of background worker and progress bar. All I can find is a simulation using the Threading.Sleep(). Samples are all working but its not working if change the simulation to actual SQL query.
Where should I insert the query in below code, please help. .NET-2.0
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
// The sender is the BackgroundWorker object we need it to
// report progress and check for cancellation.
//NOTE : Never play with the UI thread here...
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
// Periodically report progress to the main thread so that it can
// update the UI. In most cases you'll just need to send an
// integer that will update a ProgressBar
m_oWorker.ReportProgress(i);
// Periodically check if a cancellation request is pending.
// If the user clicks cancel the line
// m_AsyncWorker.CancelAsync(); if ran above. This
// sets the CancellationPending to true.
// You must check this flag in here and react to it.
// We react to it by setting e.Cancel to true and leaving
if (m_oWorker.CancellationPending)
{
// Set the e.Cancel flag so that the WorkerCompleted event
// knows that the process was cancelled.
e.Cancel = true;
m_oWorker.ReportProgress(0);
return;
}
}
//Report 100% completion on operation completed
m_oWorker.ReportProgress(100);
}
By "the query", it sounds like you only have one operation to do. This makes a progress bar tricky, since there is no way of really measuring the progress of an SQL query. It can't tell you how much longer it is going to be. You might just want to use the non-committal infinite scrolling busy indicator while you perform the query.
You should execute any sql query, which is the real task of this background worker, just after checking for CancellationPending.

BackgroundWorker - CancellationPending changing to false in RunWorkerCompleted. Why?

After canceling the BackGroundWorker, in the DoWork, the CancellationPending is true but when he comes to the RunWorkerCompleted, the CancellationPending is false. I dont know what did I do wrong?
static BackgroundWorker b1;
static void Main(string[] args)
{
b1=new BackgroundWorker();
b1.DoWork += new DoWorkEventHandler(work1);
b1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(completed);
b1.WorkerSupportsCancellation = true;
b1.RunWorkerAsync("Hellow");
Console.ReadLine();
}
private static void completed(object sender, RunWorkerCompletedEventArgs e)
{
if (((BackgroundWorker)sender).CancellationPending)
Console.WriteLine("Canceled!");
else
Console.WriteLine("Result:" + e.Result);//it goes here every time
}
private static void work1(object sender, DoWorkEventArgs e)
{
((BackgroundWorker)sender).CancelAsync();
if (((BackgroundWorker)sender).CancellationPending)
{
e.Cancel = true;
}
}
By the way, How can I add an error that occur in the DoWork to the RunWorkerCompletedEventArgs.Error for shoing it up to the user?
Yes, the BackgroundWorker class sets the CancellationPending property to false before raising the RunWorkerCompleted event. Whether or not the worker was actually cancelled.
This is quite intentional, it stops you from falling into a nasty trap that's always around when you use threads. Code that uses threads often misbehaves randomly and unpredictably due to a kind of bug called "threading race". It is a very common kind of bug and dastardly difficult to debug.
What can easily go wrong in your intended approach if BGW didn't do this is that you'll assume that the worker got cancelled when you see CancellationPending set to true. But that's an illusion, you cannot tell the difference between it being cancelled and it completing normally. The corner case is you calling CancelAsync() a microsecond before the worker completes. The worker never has a chance to even see the CancellationPending flag set to true, it was busy finishing the last bits of the DoWork event handler method. That's a threading race, the worker raced ahead of your call and completed normally.
The proper hand-shake that avoids this bug is your worker setting e.Cancel to true when it sees the CancellationPending property set to true. And of course stopping what's its doing. Now it is reliable, the e.Cancelled property in the RunWorkerCompleted event handler is a copy of e.Cancel. So your code can now reliably tell you whether or not the worker saw the cancel request.
I believe the CancellationPending property is for use during the background operation (in your work1 method). It will tell the background worker that you have requested the background operation be canceled. Once the RunWorkerCompleted event is called, the background worker has done the work to cancel the request, and therefore the cancellation is no longer pending.
EDIT: the RunWorkerCompletedEventArgs has a Cancelled property that will tell you if the background operation was cancelled.
If you throw an exception from the DoWork method (work1 in your case), it should be caught by the BackgroundWorker and populate the Error property of the RunWorkerCompletedEventArgs.

Awaken a task while sleeping

I have a task that runs periodically 10 second. I do some picturebox refreshing processes by reading database. What i want is to invoke or awaken the thread and do the refresh operation when i click a button immidiately. In short, i want the refresh task to be driven by not only time but also event together. Is this possible? If yes, how? The code block for the task is shown below.
while (true)
{
// do some refresh operation
Thread.Sleep(10000);
}
void button1_Click(object sender, EventArgs e)
{
// invoke or awaken thread
}
First off I'd advise you to drop the Thread + Sleep + Invoke combo for timed operations. It's very ugly. There are timer classes for both WinForms and WPF to do these three things automatically (update the GUI periodically from the dispatcher thread). Check out System.Windows.Forms.Timer and System.Windows.Threading.DispatcherTimer.
Now for your specific question, you could simply define a common method for updating the GUI with what you need and call it both from the timer code and from a button handler.
Create an AutoResetEvent:
protected AutoResetEvent _threadCycle;
_threadCycle = new AutoResetEvent(false);
when you want to wait do:
_threadCycle.WaitOne(delay, false);
and when you want to set the event, effectually letting the thread to continue:
_threadCycle.Set();
BONUS:
when you do _threadCycle.WaitOne(delay, false); you will get a return value, true or false, that you can check to see if the timeout did expire or you are continuing because of the manually set event.
BTW:
that will ONLY work if you are doing your task in an alternate thread. If you use main thread, you will get stuck with waiting for the timeout completion anyway. Maybe it will be the best to use #Tudors answer, and get this option only as 'through the thorns' way.
You should use a AutoResetEvent for this.
What you do is something like (assuming your AutoResetEvent is called 'signal'):
while (true)
{
signal.WaitOne(10000);
...
}
And in your button handler, just do:
signal.Set();

Need help to stop the BackgroundWorker thread

Need help to stop the BackgroundWorker thread.
I am trying to stop a background worker thread. This is what I am doing:
On stop button click (UI layer):
if (backgroundWorker.IsBusy == true &&
backgroundWorker.WorkerSupportsCancellation == true)
{
backgroundWorker.CancelAsync();
}
On DoWork event (UI layer):
if ((backgroundWorker.CancellationPending == true))
{
e.Cancel = true;
}
else
{
//Function which progresses the progress bar is called
//here with its definition in business layer
}
Once the DoWork event is fired and my program control is in the function defined in Business layer, how do I revert back to the DoWork event to set ‘e.Cancel = true’?
Setting e.Cancel does nothing, if CancellationPending is true you need to basically break out of DoWork() using return or whatever (after you've stopped what you're doing).
Something like:
private void DoWork(...)
{
// An infinite loop of work!
while (true)
{
// If set to cancel, break the loop
if (worker.CancellationPending)
break;
// Sleep for a bit (do work)
Thread.Sleep(100);
}
}
DoWork() executes in a seperate thread to the UI thread, you can report back to the UI thread using BackgroundWorkr.ReportProgress().
DoWork will run in it's own thread and is not dependant of the GUI thread.
You do almost everything correct. From the GUI thread, set the CancellationPending to true.
In the DoWork method, you probably have a loop of some sort.
Here you check if CancellationPending == true, but in addition to setting e.Cancel to true, also include a return call to make the method return and effectively stopping the worker. This also causes the WorkerCompleted event to fire on the GUI thread if the method is hooked up.
If the DoWork method perform some long task that is not divided into parts (for example if your DoWork method looks like this:
void DoWork( (object sender, DoWorkEventArgs e)
{
myClass.SomeLongOperation();
}
Then you are out of luck, since you need to manually check the CancellationPending inside the DoWork method to be able to stop it. If the DoWork itself "hangs" and waits for a operation you can't control, you can't stop it (in any orderly fashion) by setting CancellationPending from the GUI thread.
Once the DoWork event is fired and my program control is in the function defined in Business layer, how do I revert back to the DoWork event to set ‘e.Cancel = true’?
You don't. If you want cancellation to be possible during execution of your business layer, then your business layer must support cancellation. So, you have two options:
In your DoWork method, call only short-time business layer methods and check for CancellationPending in between.
Make your business layer methods cancellation-aware, i.e., pass the BackgroundWorker to them and have them periodically check CancellationPending (and retun, once it turns true).
Keep checking the CancellationPending=True Flag in the else part of your logic, and return when true.
The code e.Cancel = true only sets a state on the BackgroundWorker, so that it knows it has been cancelled, it doesn't actually cancel the process.
You'll have to check the CancellationPending inside the loop of your method and break it or return.
void DoWork(object sender, DoWorkEventArgs e) {
for (int i = 0; i < length; i++) {
if(e.CancellationPending) {
return;
}
// Long running code
}
}

Categories

Resources