Backgroundworker: ProgressChanged runs AFTER Job is done

F

Frank Dietrich

Daniel,

I am having a guided dialog consisting of a collection of panels.
Normally I am loading each "page" as it's needed. My idea now was to
employ the backgroundworker and (pre)load pages in the background.

As I am not supposed to touch the GUI from the DoWork, DoWork actually
does nothing more than a for-loop that calls ReportProgress with the
panel that needs to be added as an additional Parameter.

This is all there is:

private void Loader_DoWork(object sender, DoWorkEventArgs e)
{
// let's get the Backgroundworker from the "Sender"
BackgroundWorker oLoader = (BackgroundWorker)sender;

// and check how many pages need to be added
int nPMax = (int)e.Argument;

if (nPMax > 0)
{
for (int nPage = 1; nPage <= nPMax; nPage++)
{
oLoader.ReportProgress(nPage/nPMax * 100, nPage);
}
}
}

In my sample nPMax is 4 and if I use the debugger ReportProgress gets
called with 1, 2, 3 and finally 4 ( surprisingly ;-) ).

In the ProgressChanged I am actually adding the Panel as I have read
that it's safe to touch the GUI here.

As You can see, no rocket-science there either:

private void Loader_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
// witch Page should be added
int nI = (int)e.UserState;
this.AddPage(nI);

// MessageBox.Show(
"ProgressChanged "+ e.ProgressPercentage.ToString() +" - "+
e.UserState.ToString() );

// If we added the first page, display it to the user
// so that he can start to work
if ( nI == 1 )
{
this.NavigateTo(nI);
}
}


The weird thing is: the for-loop runs through calling ReportProgress
with the correct values but Progresschanged does not even fire once
UNTIL the loop and thus the DoWork-Job is done. Then ProgressChanged
is fired 4 times but always receiving 100% and the Value 4.

I am definitely standing on the line somewhere here. Maybe I should
try to give the worker some idle-Time (sleep)?

Any helb will be appreciated.

Regards from Berlin

Frank
 
D

Daniel Moth

There are a number of points I will make on your post, please treat each one
separately:

1. It is not weird that your DoWork completes before the progress changed
events *given* that your background work completes so fast. If you got the
WorkCompleted before the progresschanged events then that would be weird and
I'd be interested.
2. Using the BW or indeed a thread directly for your scenario will hurt your
overall performance.
3. You are not using the ProgressChanged event in the way it is intended to.

1. You don't need any Sleep or DoEvents or anything like that. Your
background work completes so fast that the progresschanged events don't have
a chance to run before it. Essentially you are not doing any slow work and
there is no need to reportprogress! Any implementation that forced the
progresschanged event to fire would actually slow down the overall
performance.

2. Let's think about what you are doing. If you want, take away the BW from
the equation and let's say you are explicitly directly using a thread. It
runs and doesn't do any work, it just calls you back on the UI thread where
the real work is done. How is that different to doing the work in the main
UI thread in the first place? It will hurt your perf rather than improve it.
If you need to keep your UI responsive while doing some work, that work has
to be done on a background thread, not marshalled back to the UI. In your
thread you are only calling ReportProgress; what is the point?

3. This one could be debatable. None of us is surprised the event is fired 4
times as that is how many times you call ReportProgress. You are surprised
you got the same data/arguments each time but I am not: that is a
side-effect of my design decision. ProgressChanged is raised for updating a
progressbar or some counter or pass a status text etc. It is *not* intended
for passing discrete state objects that are needed by the event handler.
Basically, if ReportProgress is called before the previous progress event
has raised, the previous data is overwritten. This means that your
progressbar/counter can jump from 34% to 45% for example which is normal
(you don't really care about the percentages in between: it is just info not
critical data). I did have a hard think about locking the data internally in
a queue (like I describe on another blog post) so the client never misses
progresschanged event data but I decided to err on the side of performance
since that is the scenario BW is used in: performance sensitive scenarios.
As with every design decision, there will be some that desire the opposite.
I might publish the changes need to the code to make BW satisfy that
scenario...

If you have any questions feel free to post back.

Cheers
Daniel
 
F

Frank Dietrich

Daniel,

thanks for Your quick Answer.

no, WorkCompleted behaves as it should. It comes up after everything is
done.


Yes, I know this is rubbish. I'm adding the pages in the GUI-thread
where I could have done it directly. This is just "see what
happens"-playing now. The Idea I had a few days ago, when I found Your
BackgroundWorker was to show the user the first page, so that he can
start to work and while he does, load the other pages in the background
(e.g. another thread). When I first quickly read over Your documentation
I thought, I could do that from within the "DoWork" method, thus the
other thread. At that stage I had the "build-logic" in the DoWork method
and ProgressChanged only did show progess-information.

After some reading (and testing ;-) ) I found out that I am not supposed
to touch the GUI from the BW-Thread and so I moved the logic to the
ProgressChanged in order to "see what happens", knowing that this would
make the whole construction senseless.

I will use the Backgroundworker for another task: I need to make a
request to a webservice that takes some time and returns quite some XML.
However, once I have the XML, I need to fill a listbox with the data.
And it would be nice if that could happen in the background with an
active "CANCEL" - button in the GUI.

Problem now is: I still don't know if and how I could possibly add
additional GUI-Elements (like my panels) from another thread.


Thanks again

Frank
 
D

Daniel Moth

The problem with trying to populate UI elements on a thread (e.g. panels in
your case) is that it is simply not supported (and it is often requested).

So you have 3 choices:
1. Load everything at startup (annoying for the user to start with but your
app is very responsive after)
2. Load only what is absolutely necessary on demand (spread the "slowness")
and optionally cache it for future use
3. Do as much work as possible in a thread and Control.Invoke (or use BW) at
the last minute for simple databinding-style UI update

1 and 2 are obvious and most have trouble with number 3.

The only advise I can offer is to design your app for this scenario. For
example, I have a listview that needs to be populated with a largish amount
of data. In the background thread I populate an ArrayList with ListViewItems
(which are also created and populated in the thread) and marshals back to
the UI thread the arraylist from which the listview is populated.

Naturally each GUI use case needs its own design and in some cases (I
suspect populating panels is one of those) a thread doesn't help and options
1 & 2 are the only ones you've got.

Cheers
Daniel
 
F

Frank Dietrich

Daniel,

thanks again for Your answer (and patience)

Yes, that's what I'm doing at the moment. The User gets the first panel
to start work with and when he clicks "Next" the following panel is
loaded. Works quite well. I just thought I'd probably be able to tweak
that a little in an easy way (I am an experienced VFP-Developer but more
or less new to C# and the CF with its additional limitations).


I'll try something like this for my *second task*. It might not be
working right from the start when I try it, but googling around normally
helps a lot ;-)

Thanks

Regards from Berlin (Snow -3°C)

Frank
 

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