async events too quick for textbox rendering... (2.0)

T

Tim_Mac

hi,
i have a backgroundworker class which recursively iterates through a
directory, reading the files and folders, and using ReportProgress to
inform the UI that a new file has been read. the Progress_Changed
method appends the filename to a textbox.
the problem is that the events happen so fast that the textbox never
actually gets time to display properly as it udpates, the text is never
displayed until it's all over. all you can see on screen is the
scrollbar getting smaller and smaller.
i would like the user to be able to see the progress as it happens, as
is the point of doing the thing asynchronously in the first place!

what can i do?

here is my code:

private void thread_DoWork(object sender, DoWorkEventArgs e)
{
foreach(string path in this.Paths) // 'Paths' is a List<string>
{
if(Directory.Exists(path))
RecurseFolder(path);
else if(File.Exists(path))
this.thread.ReportProgress(0, path);
}
}

private void RecurseFolder(string path)
{
// ...
}

private void thread_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
this.txt1.Text += e.UserState.ToString() + "\r\n";
}

thanks
tim
 
O

Otis Mukinfus

hi,
i have a backgroundworker class which recursively iterates through a
directory, reading the files and folders, and using ReportProgress to
inform the UI that a new file has been read. the Progress_Changed
method appends the filename to a textbox.
the problem is that the events happen so fast that the textbox never
actually gets time to display properly as it udpates, the text is never
displayed until it's all over. all you can see on screen is the
scrollbar getting smaller and smaller.
i would like the user to be able to see the progress as it happens, as
is the point of doing the thing asynchronously in the first place!

what can i do?

here is my code:

private void thread_DoWork(object sender, DoWorkEventArgs e)
{
foreach(string path in this.Paths) // 'Paths' is a List<string>
{
if(Directory.Exists(path))
RecurseFolder(path);
else if(File.Exists(path))
this.thread.ReportProgress(0, path);
}
}

private void RecurseFolder(string path)
{
// ...
}

private void thread_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
this.txt1.Text += e.UserState.ToString() + "\r\n";
// **** try adding the line below it should
// cause the TextBox to update
// ****
Application.DoEvents();
}

thanks
tim

Otis Mukinfus
http://www.otismukinfus.com
http://www.tomchilders.com
 
T

Tim_Mac

hi otis,
thanks for the suggestion. i tried adding DoEvents() into the
threading code right after i call UpdateProgress, but the behaviour is
the same. I tried adding it into the Progress_Changed handler after i
update the textbox, and i get a stack overflow exception :) the CLR
thinks i'm in an infinite loop. this is definitely not the case. i
checked the stack when the exception happens and there are about 50
Progress_Changed events stacked up, separated by 'External Code' which
corresponds to the threading code invocation of ReportProgress().

there are 400 files and folders that i'm scanning, this is not a
massive number, although they are read very quickly, 2 or 3 seconds.

i did find a way to work around it. if i call ScrollToCaret() or
Update() on the textbox, then it does update in real time, but the form
is still totally unresponsive during this time and it looks very poor,
with all the labels and buttons needing to be re-drawn.
 
M

Mike Edgewood

First, I might suggest that you not call DoEvents from a background
thread. I believe the documentation says you may end up in an infinite
loop.

Can you try this: (VB, Sorry)
Dim worker As BackGroundWorker = Directcast(sender, BackGroundWorker)
worker.ReportProgress(0, path)

or simply
DirectCast(sender, BackGroundWorker).ReportProgress(0, path)

I think the "this.thread" call might have something to do with it.

can you post what you are doing inside ReportProgress?
 
T

Tim_Mac

hi mike,
i'm already using the backgroundWorker object through the method
sender, the code i posted was slimmed down for clarity, but i should
have been more clear.

the code in Progress_Changed is as follows:

this.txtInclude.Text += e.UserState.ToString() + "\r\n";

to at least get the text box to update in real time, i changed it this:

this.txtInclude.Text += e.UserState.ToString() + "\r\n";
this.txtInclude.SelectionStart = this.txtInclude.Text.Length - 1;
this.txtInclude.ScrollToCaret();

i'm quite disappointed in the .net 2.0 Form stability. i can
understand that i am not exactly being kind to the form by shooting in
so many events, but i would have thought that UI responsiveness was
paramount whatever about if the textbox has time to render itself
between each method.
 
M

Mike Edgewood

I think I found your problem.
change
this.txtInclude.Text += e.UserState.ToString() + "\r\n";
to
this.txtInclude.AppendText(e.UserState.ToString() + "\r\n");

I belive you can eliminate the selectionstart and scrolltocaret too.

Let me know if this helps.
 
T

Tim_Mac

hi Mike,
thanks for the tip about AppendText(). it does the job nicely.
however, the form itself and all the other controls are still totally
dead while the progress is being reported. the buttons and labels are
blanked out, and the form window cannot be moved around the screen.

as a test, i added in Thread.Sleep(50) to the threading code in between
each iteration, and with this the form responds and updates properly.

i can only conclude that there are too many events for the form
rendering engine to cope with. bit of a shame.

thanks for your help, and i guess this is a disappointing conclusion
unless you have any suggestions.
tim
 
M

Marc Gravell

Another view:

If it is too quick to render, then it is too quick to read.

Why update the UI on every event? Perhaps just update form variables
(presumably a string) in the event, and then update the UI from the
variables less frequently (say: every 200ms); you'd need to do a little
locking to prevent the events changing the variables while you are updating
the UI, but other than that it could be a far more stable mechanism.

Marc
 
T

Tim_Mac

yeah you're right, it's just i'm disappointed that the application
completely loses responsiveness without me breaking any multi-threading
rules.

your bulk update approach is a good idea. thanks.
 

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