How do I reuse a BackgroundWorker object?

T

Tom P.

I have an application that uses a BackgroundWorker to get a list of
files. The user can navigate away from this to a different directory
so there needs to be a way to interrupt the BackgroundWorker and
restart with a different base path.

The problem I have is if I check IsBusy and call CancelAsync() and
wait for the backgroundWorker the wait loop is too tight for the
BackgroundWorker to communicate with the UI thread (unless I use
DoEvents). If I don't check IsBusy, just call CancelAsync and restart
the process the UI thread gets the cancel flag set after the second
DoWork call and the BackgroundWorker is never able to start the second
process.

Any ideas on how to reuse a BackgroundWorker?

Thanks,
Tom P.
 
T

Tom P.

Hello,


Not sure what is the purpose of this loop. AFAIK you still get a completed
event when cancelling so you could be able to just restart the background
worker from this event if it make sense...

The BackgroundWorker has an IsBusy property that should be checked to
determine if the thread is already busy processing. So, to wait for
the BackgroundWorker to finish you would wait in a loop, like so...

_worker.CancelAsync()
while(_worker.IsBusy)
{ }

//Next line of code.


But, since the RunWorkerCompleted event fires on the UI thread, if I
keep the UI thread busy waiting for a cancel then the UI thread won't
have the opportunity to set the canceled flag. If I don't wait there
is no way for the UI thread to determine which call to RunWorkerAsync
is being canceled, so they both are.

Tom P.
 
F

Family Tree Mike

The BackgroundWorker has an IsBusy property that should be checked to
determine if the thread is already busy processing. So, to wait for
the BackgroundWorker to finish you would wait in a loop, like so...

_worker.CancelAsync()
while(_worker.IsBusy)
{ }

//Next line of code.


But, since the RunWorkerCompleted event fires on the UI thread, if I
keep the UI thread busy waiting for a cancel then the UI thread won't
have the opportunity to set the canceled flag. If I don't wait there
is no way for the UI thread to determine which call to RunWorkerAsync
is being canceled, so they both are.

Tom P.


It seems as if you should be placing everything you have after the call
to CancelAsync, into the handler for the RunWorkerCompleted event.
 
P

Patrice

But, since the RunWorkerCompleted event fires on the UI thread, if I
keep the UI thread busy waiting for a cancel then the UI thread won't
have the opportunity to set the canceled flag.

So what if you just let the RunWorkerCompleted event to happen rather than
keeping the UI thread busy with this loop ? You should have a property that
allows to check if this is a normal termination or if it happened through
cancellation so I'm not sure you would need your own flag for this...
If I don't wait there
is no way for the UI thread to determine which call to RunWorkerAsync
is being canceled, so they both are.

Not sure what you mean. AFAIK you can't calll RunWorkerAsync again if the
BackgroundWorker is still busy so you can only cancel the current work

So to try to be more accurate despite my English, my approach would be :
- when you need to cancel just do that and set a flag that tells you need a
next run
- when the RunWorkerCompleted event happens, you know you need a next run
and you could well be able to call RunWorkerAsync again from the
RunWorkerCompleted event (this is IMO the risky part of my architecture,
depends if the BackgroundWorker is considered still busy in this event but
hopefully...).

If the later point is ok I believe this approach would be quite easy to
code.

As this point it's likely better to work on the minimal amount of code
needed to show this point. Let me know if you have something to show. I'll
try on my side but right now I don't have C# handy as I'm already
uninstalling old stuff to clean up the place for 2010 ;-)
 
P

Peter Duniho

Tom said:
I have an application that uses a BackgroundWorker to get a list of
files. The user can navigate away from this to a different directory
so there needs to be a way to interrupt the BackgroundWorker and
restart with a different base path.

How is this different from the last time you asked this question? Why
are you not just continuing in the same message topic?
The problem I have is if I check IsBusy

I already explained that you should not check IsBusy. There's no point
to doing that.
and call CancelAsync() and
wait for the backgroundWorker the wait loop is too tight for the
BackgroundWorker to communicate with the UI thread

I also already explained that you should not have a loop where you wait
for the BackgroundWorker.

I also already explained that it's not a question of the loop being "too
tight". It's that the BackgroundWorker needs the thread that you are
tying up in order to complete.
(unless I use
DoEvents). If I don't check IsBusy, just call CancelAsync and restart
the process the UI thread gets the cancel flag set after the second
DoWork call and the BackgroundWorker is never able to start the second
process.

I also already explained how to properly reuse a BackgroundWorker
instance, by not trying to restart it until your RunWorkerCompleted
event handler has been called.

I also already even posted a code example showing exactly how to do this.

Why are you starting this question all over again?

Pete
 
P

Peter Duniho

Peter said:
[...]
Why are you starting this question all over again?

Ah. I think I see the problem. You are using Google's crappy news
service to read these newsgroups. They apparently didn't get my post.

Here's an archived copy of the thread (with my post) at a different site:
http://www.dotnetmonster.com/Uwe/Fo...kgroundWorker-Cancel-only-works-with-DoEvents

Here's a link to the same thread, but on Microsoft's web site:
http://www.microsoft.com/communitie...9380538fa8a9&lang=en&cr=US&sloc=en-us&p=1#top

Hope that helps.

Pete
 
P

Patrice

So with a listbox it would give something such as the code below. Main
points are :
- when an item is selected if the backgroundworker is not busy, it is
launched, if busy then a flag is set and it is cancelled
- when it completes, if the flag is set it is launch again from the
completed event
- the background work just keep the thread busy until the duration passed as
an argument is reached (2,4,8 or 16 allowing to play with more or less long
background works). Cancellation is checked every second...


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Diagnostics;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private bool runWhenCompleted;
public Form1()
{
InitializeComponent();

Object[] values = { 2, 4, 8, 16 };
listBox1.Items.AddRange(values);
listBox1.SelectedIndexChanged+=new
EventHandler(listBox1_SelectedIndexChanged);

backgroundWorker1.DoWork+=new
DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted+=new
RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
}

private void listBox1_SelectedIndexChanged(object sender,EventArgs
e)
{
if (!backgroundWorker1.IsBusy)
backgroundWorker1.RunWorkerAsync(listBox1.SelectedItem);
else
{
runWhenCompleted = true;
backgroundWorker1.CancelAsync();
}
}
private void backgroundWorker1_DoWork(object sender,DoWorkEventArgs
e)
{
const double UIDelay = 1.0;
BackgroundWorker bw = (BackgroundWorker) sender;
int Duration = (int)e.Argument;
Debug.WriteLine("DoWork : " + Duration.ToString());
Stopwatch workWatch = new Stopwatch();
Stopwatch UIWatch = new Stopwatch();
workWatch.Start();
UIWatch.Start();
while (workWatch.Elapsed.TotalSeconds<=Duration)
{
if (UIWatch.Elapsed.TotalSeconds>=UIDelay)
{
if (bw.CancellationPending)
{
e.Cancel = true;
break;
}
UIWatch.Restart(); // .NET 4.0 only does
Stop/Reset/Start...
}
}
e.Result = workWatch.Elapsed.TotalSeconds;
}
private void backgroundWorker1_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
BackgroundWorker bw = sender as BackgroundWorker;
if (e.Cancelled) Debug.WriteLine("RunWorkerCompleted
(Cancelled)");
else Debug.WriteLine("RunWorkerCompleted
("+e.Result.ToString()+")");
if (runWhenCompleted)
{
runWhenCompleted = false;
bw.RunWorkerAsync(listBox1.SelectedItem);
}
}
}
}
 
T

Tom P.

So what if you just let the RunWorkerCompleted event to happen rather than
keeping the UI thread busy with this loop ? You should have a property that
allows to check if this is a normal termination or if it happened through
cancellation so I'm not sure you would need your own flag for this...


Not sure what you mean. AFAIK  you can't calll RunWorkerAsync again if the
BackgroundWorker is still busy so you can only cancel the current work

Actually, since BackgroundWorker uses the thread pool you very much
can start RunWorkerAsync() even if the BackgroundWorker is busy.

So to try to be more accurate despite my English, my approach would be :
- when you need to cancel just do that and set a flag that tells you needa
next run
- when the RunWorkerCompleted event happens, you know you need a next run
and you could well be able to call RunWorkerAsync again from the
RunWorkerCompleted event (this is IMO the risky part of my architecture,
depends if the BackgroundWorker is considered still busy in this event but
hopefully...).

If the later point is ok I believe this approach would be quite easy to
code.

As this point it's likely better to work on the minimal amount of code
needed to show this point. Let me know if you have something to show. I'll
try on my side but right now I don't have C# handy as I'm already
uninstalling old stuff to clean up the place for 2010 ;-)

The problem is not how to start the BackgroundWorker again, rather,
how to interrupt the old one and wait until it's finished.

Tom P.
 
T

Tom P.

Peter said:
[...]
Why are you starting this question all over again?

Ah.  I think I see the problem.  You are using Google's crappy news
service to read these newsgroups.  They apparently didn't get my post.

Yeah, well... it's work. What am I gonna do. Thanks for understanding.

Here's an archived copy of the thread (with my post) at a different site:http://www.dotnetmonster.com/Uwe/Forum.aspx/dotnet-csharp/95856/Backg...

Here's a link to the same thread, but on Microsoft's web site:http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg...

Hope that helps.

Pete

That it does! Basically double-up on the flags. I'll give it a try but
I'll tell you, more often than not I'm running into race conditions.
The Worker will get a cancel but RunWorkerCompleted won't get called
right away. Let's see what happens if I put the restart code in the
RunWorkerCompleted() event.

Give me a minute.
Tom P.
 
T

Tom P.

Peter said:
[...]
Why are you starting this question all over again?

Ah.  I think I see the problem.  You are using Google's crappy news
service to read these newsgroups.  They apparently didn't get my post.

Here's an archived copy of the thread (with my post) at a different site:http://www.dotnetmonster.com/Uwe/Forum.aspx/dotnet-csharp/95856/Backg...

Here's a link to the same thread, but on Microsoft's web site:http://www.microsoft.com/communities/newsgroups/en-us/default.aspx?dg...

Hope that helps.

Pete

HA! That's exactly what I wanted. Thanks Pete.

I look at IsBusy and if I need to I set a restartSetPath flag and then
CancelAsync().
In RunWorkerCompleted if this was Cancelled then I call DoSetPath from
there.

Works like a charm.

I knew there was a right way to do this.
Thanks again.
Tom P.
 
P

Peter Duniho

Tom said:
[...]
I look at IsBusy and if I need to I set a restartSetPath flag and then
CancelAsync().
In RunWorkerCompleted if this was Cancelled then I call DoSetPath from
there.

Works like a charm.

I knew there was a right way to do this.

Glad you got it to work.

Note: be very careful to _only_ to the IsBusy check in your GUI thread,
the same one where your RunWorkerCompleted event handler will be
executed. Without doing that, you'd have a potential race condition in
which you set your "restartSetPath" flag but only after the
RunWorkerCompleted event handler executes, thus causing the flag to not
be handled correctly in the event handler.

As long as you do all of your controlling logic of the BackgroundWorker
in the main GUI thread where the BackgroundWorker was created, there
should be no problem. This is the normal, likely usage pattern for
BackgroundWorker anyway, so your code is already doing it that way.

Pete
 
P

Patrice

Actually, since BackgroundWorker uses the thread pool you very much
can start RunWorkerAsync() even if the BackgroundWorker is busy.

From http://msdn.microsoft.com/en-us/library/h01xszh2(v=VS.80).aspx :

"If the background operation is already running, calling RunWorkerAsync
again will raise an InvalidOperationException."

And this is what I alway saw...
The problem is not how to start the BackgroundWorker again, rather,
how to interrupt the old one and wait until it's finished.

Have you tried Peter's or my own code sample ? Feel free to ask a specific
question if you still have issues... (both codes are using CancelAsync and
let the Completed even to happen rather than looping).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top