BackgroundWorker Question

P

Peter Carlson

I am testing backgroundWorker to update our installer with both messages
and with a progress bar. I am finding however that the text never gets
updated. Any ideas what I might be doing wrong? Code snipped for brevity.

Peter

private BackgroundWorker worker;
private void Install_Click(object sender, EventArgs e)
{
worker = new BackgroundWorker();
worker.RunWorkerCompleted += new
RunWorkerCompletedEventHandler(worker_WorkerCompleted);
worker.ProgressChanged += new
ProgressChangedEventHandler(worker_ProgressChanged);
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.WorkerReportsProgress = true;
worker.WorkerSupportsCancellation = true;
worker.RunWorkerAsync();
}

void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
txtInfo.Text = e.ProgressPercentage.ToString();
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = (BackgroundWorker)sender;
for (int i = 0; i < 1000000; i++)
{
bw.ReportProgress(i);
}
}
 
R

Rene

Refrsh the textbox.

void worker_ProgressChanged(object sender, ProgressChangedEventArgs
e)
{
txtInfo.Text = e.ProgressPercentage.ToString();
txtInfo.Refresh();
}
 
P

Peter Carlson

Thx.
Refrsh the textbox.

void worker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
txtInfo.Text = e.ProgressPercentage.ToString();
txtInfo.Refresh();
}
 
P

Peter Duniho

Refrsh the textbox.

void worker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
txtInfo.Text = e.ProgressPercentage.ToString();
txtInfo.Refresh();
}

No, don't do that.

If you have set the Text property correctly, the TextBox will invalidate
itself and be repainted automatically. In correct code, you don't need to
call Refresh().

If calling Refresh() does fix the problem, it means that your GUI thread
has for some reason gotten hung up in spite of your use of the
BackgroundWorker class. Why that might be exactly is impossible to say
without a complete code example. But it's definitely what's happening if
that fixes the problem.

I will also point out that the docs say that you should only pass a number
between 0 and 100 to BackgroundWorker.ReportProgress(). I don't think
that's related to the problem you're having; my own test shows that you
can integers out of that range. But you should consider carefully whether
you really want to violate the documentation's description of that
parameter.

If you can't figure out why your GUI thread is blocking, you should posta
concise-but-complete code sample that reliably demonstrates the problem.

Pete
 
R

Rene

mmmm…. I should have tested this before postign anything….. any way, the
sample that he posted is a working sample and the problem can be easily
reproduced (at least on my computer).

My guess is that the loop he is doing on the “DoWork†method is pushing so
many calls to the UI thread that it must be getting overwhelmed or
something.

I added:

System.Threading.Thread.Sleep(1);

Inside the loop and that fixed the problem. So I am assuming that all the UI
thread needs is a little time to breath.

I also think that your remark about the percentage being form 0 to 100 is
very valid. If you limit reporting the progres from 0 to 100 (and you only
ReportProgress when the value actually changes) you can eliminate most of
the calls to the UI thread giving it time to do what it needs to do.

Regards.


Refrsh the textbox.

void worker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
txtInfo.Text = e.ProgressPercentage.ToString();
txtInfo.Refresh();
}

No, don't do that.

If you have set the Text property correctly, the TextBox will invalidate
itself and be repainted automatically. In correct code, you don't need to
call Refresh().

If calling Refresh() does fix the problem, it means that your GUI thread
has for some reason gotten hung up in spite of your use of the
BackgroundWorker class. Why that might be exactly is impossible to say
without a complete code example. But it's definitely what's happening if
that fixes the problem.

I will also point out that the docs say that you should only pass a number
between 0 and 100 to BackgroundWorker.ReportProgress(). I don't think
that's related to the problem you're having; my own test shows that you
can integers out of that range. But you should consider carefully whether
you really want to violate the documentation's description of that
parameter.

If you can't figure out why your GUI thread is blocking, you should post a
concise-but-complete code sample that reliably demonstrates the problem.

Pete
 
P

Peter Duniho

mmmm…. I should have tested this before postign anything….. any way, the
sample that he posted is a working sample and the problem can be easily
reproduced (at least on my computer).

It's not a working sample. It's a subset of a working sample.
Nevertheless, since you took the time to incorporate it into a complete
application, you were able to uncover useful information. :)

Personally, I don't like to bother messing with incomplete samples because
there's never any guarantee that the effort will be rewarded, but in this
case your effort paid off, at least in that you were able to reproduce the
problem and have added to the available information. So thank you for
that. Hopefully the OP will come back and see the answer. Which is...
My guess is that the loop he is doing on the “DoWork†method is pushing
so many calls to the UI thread that it must be getting overwhelmed or
something.

Sort of. I went ahead and coded my own sample, to look at what's
happening. On my computer, the UI updates to some degree, but never all
the way. I don't believe it's the UI thread that's getting overwhelmed
though. It has more to do with the ordering of the invocations and the
paint messages.

Basically, the background thread completes relatively quickly. On my
computer, it's in the neighborhood of between 7 and 9 seconds (and it only
takes that long because it has to share time with the GUI thread, which is
busily trying to keep up). But by the time it completes, it has of course
enqueued 1 million progress updates to the GUI.

This doesn't really "overwhelm" the GUI thread, in that it can't do
anything at all. But because of the way updating happens, the GUI winds
up stuck processing most of those million progress updates (or all, if the
computer is fast enough...mine's not :) ) before _any_ actual visual
update gets a chance to happen.

The background worker essentially enqueues invocations for the GUI thread
each time ReportProgress() is called. Those invocations won't get to
execute until the background thread is preempted and the GUI thread gets
to run. When that happens, the GUI thread gets to process the progress
updates, but in a correctly organized GUI application, all that really
happens at that point is that the internal data is updated and the control
is invalidated. It won't actually be updated on the screen until the
paint message that is enqueued when the control is invalidated is
processed.

The OS is smart enough to not keep enqueuing paint messages every time the
control is invalidated. However, it still has the problem that if the
background thread gets to run before all of the progress changed events
have been processed, it starts queueing up _more_ progress changed events,
which all wind up ahead of any subsequently posted paint event in the
queue.

Basically, this keeps going on until the background thread has finished,
and in doing so has gotten progress changed events in the way of any
remaining paint messages.

It's not that the GUI thread isn't doing anything. It's just that it's
stuck handling the enqueued invocations, and won't get around to handling
a paint message until it's done with all the rest of them. Which takes
awhile. :)

On my computer, the actual behavior is _highly_ variable. Sometimes the
ordering of the events is such that I get updates up to a quarter million
iterations (though obviously that's not necessarily a quarter million
visual updates). Sometimes, it doesn't get a single update in before the
background thread has completely filled the queue. Most of the time, it's
somewhere in between, usually around 20-40,000 progress updates.

But the bottom line is that progress updates result in subsequent paint
messages being enqueued and if you get to a point where there are no more
paint messages left until after all of the remaining progress update
messages in the message queue, then the GUI basically will freeze until
all of those progress update messags can be handled.

Ironically, this is mostly an artifact of the otherwise-useless sample
that the OP posted (it's a useful demonstration, but it obviously doesn't
really do anything). Any normal background worker code would actually
_do_ something, rather than just beat on the ReportProgress() method. The
time spent doing something would usually prevent the background worker
from queueing too many progress updates for the GUI thread to handle in
one thread quantum, or the background worker would even yield as a natural
result of its work (for example, if it's doing i/o as part of the work).
I added:

System.Threading.Thread.Sleep(1);

Inside the loop and that fixed the problem. So I am assuming that all
the UI thread needs is a little time to breath.

That only "sort of" fixes the problem. It basically guarantees a thread
switch after each iteration, which does give the GUI thread time to
process the invoked progress update, as well as process the ensuing paint
message. This synchronizes all of the operations, and in doing so has a
devastating effect on performance. You would never want to do something
like that in a real application.

Likewise, while your other suggestion of using Refresh() forces the
control to paint itself without waiting for a paint message to come
through the message queue, it forces that visual update for every single
progress update. Again, this would be devastating on performance in any
situation where the progress updates happen so quickly that calling
Refresh() makes a difference.

Much better IMHO is your last suggestion:
I also think that your remark about the percentage being form 0 to 100
is very valid. If you limit reporting the progres from 0 to 100 (and you
only ReportProgress when the value actually changes) you can eliminate
most of the calls to the UI thread giving it time to do what it needs to
do.

I agree that this is the real solution. The basic issue, even ignoring
whether the ReportProgress() method is called with in-range data, is how
frequently the progress is updated. There should be no need to call it
hundreds of thousands of times per second as in the original example, and
of course doing so does completely screw up the performance of both
threads (it slows down the background thread by at least half, and forces
the GUI thread to do a bunch of unnecessary work).

Basically, the answer is: don't abuse the BackgroundWorker. :)

Pete
 
R

Rene

Awesome explanation Pete,

Man, I really hope that the original poster comes back and reads the post
again, I feel like such a dumb ass for posting my cheese answer.

Not to self:
Next time take some time to animalize the post and don't just post the first
thing that comes to my mind aggggggggggggggggg!!

Cheers
 
P

Peter Duniho

Awesome explanation Pete,

Man, I really hope that the original poster comes back and reads the post
again, I feel like such a dumb ass for posting my cheese answer.

Well, please don't. IMHO you went above and beyond the call of duty in
turning the OP's incomplete example into a working example and verifying
that _a_ problem existed. Honestly, that was the turning point in the
thread, where we went from having very little information to having a lot
of information that led us to a more complete answer.

In other words, we wouldn't have an answer if it hadn't been for your
efforts. It's not like what you wrote was wrong, and it's also not like
you just posted without doing any work (you created a working test
program, after all). There was just room for elaboration, is all.

Pete
 

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