Progress bar sometimes refuses to refresh (but works OK with DoEve

  • Thread starter Thread starter David
  • Start date Start date
D

David

I have a .NET 3.0 (VS2005) Winform app. The UI invokes a long-running BRL
process (synchronously, on the same thread). The BRL raises events
periodically to tell the UI to update a progress bar. Works OK, most of the
time. But if the user does something as simple as clicking on the form, the
update of the progress bar stops.

I found that calling DoEvents (rather than just doing a Refresh) in the
event handler that updates the Progress bar "fixes" the problem. But I've
read posts saying that DoEvents is evil. And I can see why. But simply doing
a Refresh in my event handler doesn't suffice. So is there another way ?
(short of reworking the app to run the BR process on a separate
thread...which introduces a different can of worms)

Here's the event handler in the UI which exhibits the problem.

Private Sub ProgressEventRaised(ByVal sender As Object, ByVal e As
ProgressEventArgs)



If e.recordsProcessed > Me.uiProgressBar.Maximum Then
Me.uiProgressBar.Value = Me.uiProgressBar.Maximum
Else
Me.uiProgressBar.Value = e.recordsProcessed
End If

Me.Refresh

End Sub

===> Replacing Me.Refresh with Application.DoEvents "fixes" it. <===

BTW: testing InvokeRequired and re-invoking the event handler doesn't fix
the problem either:

If Me.a-control-on-the-form.InvokeRequired Then
Dim d As New ProgressEventCallback(AddressOf
ProgressEventRaised)
Me.Invoke(d, New Object() {sender, e})
Exit Sub
End If

But since my UI and BR code is running on the same thread, I would have been
surprised if doing this helped.

TIA !
 
Hello,

The problem is that when the progress bar is updated it invalidates it and
queues a paint message. But, the UI thread will not process any queued tasks
until the long running task has completed. The user trying to interact with
the form also queues windows messages which causes it to appear not
responsive as the ui thread does not process them until long running task
completes.

Calling Refresh has no effect as it simply triggers another invalidate which
causes another paint message to be queued, which the UI thread won't process
until after the long running task completes.

Calling Application.DoEvents fixes the problem as it tells the UI thread to
jump out of your long running method and process all queued windows messages
(which includes any user interaction and pending paints). This isn't good
coding style though, and can be dangerous if not fully understood since it
can go off and process any queued message/event.

The best solution would be to use the System.ComponentModel.BackgroundWorker
class (see
http://msdn.microsoft.com/en-gb/library/system.componentmodel.backgroundworker.aspx)
to perform the long running task and use its ProgressChanged to report
progress (or new state) to the UI thread and its RunWorkerCompleted event to
detect task completion. This class is good as it handles the cross thread
invokes for you: DoWork is executed on a thread pool thread where as
ProgressChanged and RunWorkerCompleted are executed on the UI thread.

Hope this helps,

David
 
Then there's something fundamental I don't understand about how Windows (XP
SP3, in this case) dispatches application processes/threads...

You said that "the UI thread will not process any queued tasks [messages?]
until the long running task has completed".

But my long-running task is doing lots of I/O operations - both flat file
and SQL Server accesses. It's not in a CPU-loop. So when my long-running task
yields the processor while waiting for the I/O to complete, why doesn't
Windows then "process" (allow to execute) any queued messages (paint, etc)
for my app?

Is this behaviour due to the fact that my entire app is running on a single
thread, and so when it does I/O, Windows doesn't recognize that there's
another "part" of the app that can be dispatched?

But thanks for the clarification on how the app "should" have been written
(with a backgroundworker), although that rework will have to wait until
Version 2. Meanwhile, I'll have to deal with preventing it from "going off
and processing any queued message/event" when I call DoEvents...

Thanks again,

David T
 
Hello,

When you say your application yields the processor until the IO completes,
do you mean that your long running method call returns and then an event gets
handled when IO completes that continues task, or that you call an IO
operation that blocks the UI thread until the operation completes?

You see, when you call Application.Run that sets up a windows message loop
from which the calling thread dispatches messages. These low level windows
messages (triggered by UI interaction for example) are what cause the thread
to begin executing an event handler (e.g. button click or control paint).
Once the UI thread starts dispatching a message (and therefore any event
handlers it triggers) it will run through that event handler in its entirety
(and therefore all methods it calls, all event handlers it triggers) before
returning to dispatch the next message.

The OS can context switch to another process, or to another thread within
your process, but the UI thread will continue dispatching that one windows
message when it gets switched back.

This means it is best to keep event handlers executed by the UI thread as
short as possible so that it can get on to dispatching the next message.

Application.DoEvents can provide an escape as it says to the UI thread "go
back to the message loop and dispatch all pending messages". So any pending
messages (e.g. paints or user interaction) will be handled before the call
returns.

The only danger (which I didn't explain very well) is that you have to be
aware that this can break some assumptions about parts of your code.

e.g. an application has a button which the user clicks twice with the
following click event.

button_click(...)
{
Console.WriteLine("1");
Application.DoEvents();
Console.WriteLine("2");
}

If the application.doevents call was not there, then no matter how fast the
user clicks the button the output would always be 1212 as messages triggered
by the subsequent clicks would not get dispatched until the thread returns
from handling the previous click. However, with application.doevents you can
get the output 1122 as the message for the subsequent click can be handled
when application.doevents is called during the handling of the previous click
(and so on).

This is the behaviour I was alluding to when i said application.doevents can
be dangerous. So long as your code is safe if other events are processed
before control returns to the long running function then it is ok to use
application.doevents.

Hope this helps,

David
 
Back
Top