How can I find the UI thread from a BackgroundWorker?

T

Tom P.

My basic problem is this: I have a background process that, while the
entire process takes too long to wait for, each piece causes the UI to
update more often and still results in a locked UI thread. I'm getting
a list of filesystem objects and inserting them into a listview. Being
on a network the file list can be huge, so I execute that part on a
BackgroundWorker thread. The problem is that every single filesystem
object makes the listview resort and repaint, this takes longer than
it does to get a filesystem object causing the UI to be updating so
much it's locked.

I'm figuring the best way to handle this is to get a filesystem object
then check the main UI and see if it's busy, if it is keep looping for
filesystem objects. If it's not busy then hand over the current list
of objects and let them be inserted.

But I can't find a way of getting the UI thread. How do I find the UI
thread from a BackgroundWorker object? Do I have to inherit override
the ctor to get the thread when the object is instantiated?

What do you guys think is the best way?

Tom P.
 
P

Patrice

You could use the ReportProgress event (you have a user state arguments you
can use for whatever you want) and pass the lists of what you found since
the last update. This way you'll add those elements to your UI by batches
instead of adding each of them individually.

In particular if you check before progress reporting that the progress
actually changed (% ranging from 0 to 100) you'll guarantee that the UI
thread is called 100 times in the worst case.

Keep in mind that prupose of a background thread is to free the UI thread.
If the background thread calls frequently the UI thread you are basically
back at the same point with a busy UI thread...

Also make sure to favor AddRange over Add (as you add a whole list in one
go, it won't refresh for each and every new item).

--
Patrice


"Tom P." <[email protected]> a écrit dans le message de groupe de
discussion :
(e-mail address removed)...
 
P

Peter Duniho

Tom said:
[...]
I'm figuring the best way to handle this is to get a filesystem object
then check the main UI and see if it's busy, if it is keep looping for
filesystem objects. If it's not busy then hand over the current list
of objects and let them be inserted.

How are you going to define "busy"? Even if you can get the main UI
thread, what are you intending to use to determine whether it's okay to
deliver a list of objects?
But I can't find a way of getting the UI thread. How do I find the UI
thread from a BackgroundWorker object? Do I have to inherit override
the ctor to get the thread when the object is instantiated?

What do you guys think is the best way?

I agree with Patrice that batching up the file names is better. In your
worker thread, just keep a list of the file names to deliver to the main
UI thread. Each iteration of your loop, calculate the progress as an
integer between 0 and 100 and compare that to the progress value you
calculated last time through the loop. Only when that value changes
would you then call ProgressChanged(), passing the current list of file
names (be sure to create a new list object for the loop…once you've
passed the list object to the ProgressChanged() method, it's no longer
safe to use in your worker thread).

Pete
 
T

Tom P.

Tom said:
[...]
I'm figuring the best way to handle this is to get a filesystem object
then check the main UI and see if it's busy, if it is keep looping for
filesystem objects. If it's not busy then hand over the current list
of objects and let them be inserted.

How are you going to define "busy"?  Even if you can get the main UI
thread, what are you intending to use to determine whether it's okay to
deliver a list of objects?

I would define "busy" as non-blocking, able to paint. That's my main
concern - There are drives at work with thousands of files and
directories and if I'm navigating to one near the top I don't want to
wait for the entire list to fill out. As far as safe for delivery, I
am currently assuming that if the UI thread can handle the request -
it's safe. I do have some structure in place that will stop (and
abandon) the background thread if the user navigates away before the
list is done.
I agree with Patrice that batching up the file names is better.  In your
worker thread, just keep a list of the file names to deliver to the main
UI thread.  Each iteration of your loop, calculate the progress as an
integer between 0 and 100 and compare that to the progress value you
calculated last time through the loop.  Only when that value changes
would you then call ProgressChanged(), passing the current list of file
names (be sure to create a new list object for the loop once you've
passed the list object to the ProgressChanged() method, it's no longer
safe to use in your worker thread).

Pete

I agree that my calls to add items should be batched in some way,
that's mostly what I am looking for - a stable way to batch the calls
to add items to the list. I'd love to be able to use Wait or Join but
I'm not sure how I'd implement them.

I'll take a look at the "by percentage" you guys suggest. Thanks.

Tom P.
 
P

Peter Duniho

Tom said:
Tom said:
[...]
I'm figuring the best way to handle this is to get a filesystem object
then check the main UI and see if it's busy, if it is keep looping for
filesystem objects. If it's not busy then hand over the current list
of objects and let them be inserted.

How are you going to define "busy"? Even if you can get the main UI
thread, what are you intending to use to determine whether it's okay to
deliver a list of objects?

I would define "busy" as non-blocking, able to paint.

So, given that definition, how will you determine that the thread meets
that definition?
That's my main
concern - There are drives at work with thousands of files and
directories and if I'm navigating to one near the top I don't want to
wait for the entire list to fill out. As far as safe for delivery, I
am currently assuming that if the UI thread can handle the request -
it's safe.

That's a circular definition. Of _course_ "if the UI thread can handle
the request" that means "it's safe [to make the request]". It's true (n
a self-referential way), but it's not a specification. :(
I do have some structure in place that will stop (and
abandon) the background thread if the user navigates away before the
list is done.

I don't see how that's relevant to this particular part of the problem.
I agree that my calls to add items should be batched in some way,
that's mostly what I am looking for - a stable way to batch the calls
to add items to the list. I'd love to be able to use Wait or Join but
I'm not sure how I'd implement them.

Whatever types you mean by "Wait" or "Join", I'm sure they will not be
involved.
I'll take a look at the "by percentage" you guys suggest. Thanks.

It will look something like this (body of DoWork event handler):

BackgroundWorker worker = (BackgroundWorker)sender;
int percentPrev = 0;
int iMax = 100000;

for (int i = 0; i < iMax; i++)
{
int percentCur = (int)((float)i / iMax * 100);

// do stuff

if (percentCur != percentPrev)
{
worker.ReportProgress(percentCur);
percentPrev = percentCur;
}
}

Obviously, your actual loop will look a lot different, and you'll pass
actual data when you call ReportProgress(), but the above is the general
idea.

Pete
 
T

Tom P.

Tom said:
Tom P. wrote:
[...]
I'm figuring the best way to handle this is to get a filesystem object
then check the main UI and see if it's busy, if it is keep looping for
filesystem objects. If it's not busy then hand over the current list
of objects and let them be inserted.
How are you going to define "busy"?  Even if you can get the main UI
thread, what are you intending to use to determine whether it's okay to
deliver a list of objects?
I would define "busy" as non-blocking, able to paint.

So, given that definition, how will you determine that the thread meets
that definition?

Exactly! That was my question. How do I know when I'm "overwhelming"
the UI thread? I want the UI to paint while these big directories are
loading and give the user a chance to interact, before the load
finishes, if they want to.
That's my main
concern - There are drives at work with thousands of files and
directories and if I'm navigating to one near the top I don't want to
wait for the entire list to fill out. As far as safe for delivery, I
am currently assuming that if the UI thread can handle the request -
it's safe.

That's a circular definition.  Of _course_ "if the UI thread can handle
the request" that means "it's safe [to make the request]".  It's true (n
a self-referential way), but it's not a specification.  :(

Then I guess I don't understand your question of "How do you know it's
safe to deliver a list of objects"? Are you under the impression that
this is a stable persistent thread? No, no. This only gets spun when
the user navigates to a new directory. Other than that I don't
understand the question.

I don't see how that's relevant to this particular part of the problem.

I don't either but I thought it had something to do with the "safe to
deliver a list of objects" question from above. If not, then I don't
know what the question is asking.

Whatever types you mean by "Wait" or "Join", I'm sure they will not be
involved.

Threading.Thread.SpinWait and Threading.Thread.Join. If I had to
manage the thread marshaling myself I was looking at these two
functions but only in a very cursory "I wonder if they could help"
way. Turns out I don't need them, thanks to your suggestion.

It will look something like this (body of DoWork event handler):

   BackgroundWorker worker = (BackgroundWorker)sender;
   int percentPrev = 0;
   int iMax = 100000;

   for (int i = 0; i < iMax; i++)
   {
     int percentCur = (int)((float)i / iMax * 100);

     // do stuff

     if (percentCur != percentPrev)
     {
       worker.ReportProgress(percentCur);
       percentPrev = percentCur;
     }
   }

Obviously, your actual loop will look a lot different, and you'll pass
actual data when you call ReportProgress(), but the above is the general
idea.

Pete

I worked it out and it runs fine. Quite fast and still leaves the UI
open to react to the user.
Thanks for the help.

Tom P.
 
P

Peter Duniho

Tom said:
[...]
So, given that definition, how will you determine that the thread meets
that definition?

Exactly! That was my question.

AFAIK, there is no reliable way to.
How do I know when I'm "overwhelming" the UI thread?

That's actually a different question IMHO. That is, wanting to know
whether the UI thread is "not busy" is very different from wanting to
know whether the UI thread "has been overwhelmed".

The former is not, AFAIK, reliably able to be determined. The latter,
well…you can simply track how long it is between the time you issue a
command to the main UI thread and when it actually processes it. If
that time is of any significant length (more than, say…100ms, though
this is somewhat arbitrary), then you're overwhelming it.
I want the UI to paint while these big directories are
loading and give the user a chance to interact, before the load
finishes, if they want to.

For the best user experience, you should strive instead to make the user
interface responsive while making the background task efficient. It's
well and good to provide _some_ user feedback, but if you have to choose
between keeping the UI 100% up-to-date on the progress of the background
task and allowing the background task to run efficiently, you should
choose the latter.
That's my main
concern - There are drives at work with thousands of files and
directories and if I'm navigating to one near the top I don't want to
wait for the entire list to fill out. As far as safe for delivery, I
am currently assuming that if the UI thread can handle the request -
it's safe.

That's a circular definition. Of _course_ "if the UI thread can handle
the request" that means "it's safe [to make the request]". It's true (n
a self-referential way), but it's not a specification. :(

Then I guess I don't understand your question of "How do you know it's
safe to deliver a list of objects"?

You have specified that you want to do this "thing" (the UI update) only
when "the UI thread can handle the request". But that's just another
way of saying "when it's safe to do so".

And my question is exactly that: what was it you were thinking would be
a suitable criteria for determining that "it's safe to do so"?
Are you under the impression that
this is a stable persistent thread? No, no. This only gets spun when
the user navigates to a new directory.

I understand that. It's not the background thread that's the question.
It's what criteria are you expecting you'd use to determine the state
of the foreground thread prior to sending it an update?

Based on your latest reply, I believe that this is all moot. But I
think it's worth keeping in mind what is and is not possible to determine.
[...]
I'll take a look at the "by percentage" you guys suggest. Thanks.

It will look something like this (body of DoWork event handler):

[...]

I worked it out and it runs fine. Quite fast and still leaves the UI
open to react to the user.
Thanks for the help.

Glad you got it working!

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