Paint Event Starvation During Long Computations

  • Thread starter Thread starter Gary Brown
  • Start date Start date
G

Gary Brown

Hi,

I have a computation intensive application that tries to
update the display quite often. After a few seconds the
window stops updating and the window goes blank. A
breakpoint in the Paint event doesn't trigger.

Further, if another window obscures the display while it
is still working the display is blank after it is uncovered.
Also, when the window is blank, the program does not
respond to the keyboard.

I am not entirely sure what is happening. I am also not
sure what other information to provide.

Any advice appreciated.

FWIW: This is a PDP-1 (an early computer, for you yungins)
simulation. The console lights are updated after each
instruction. The computations proceed as expected but
the display disappears after a few seconds. I can turn the
console light update off in which case I see the second
problem when switching windows. I can also slow the
update down to every 1000 instructions (because the
console update dominates the execution time but you still
want to see blinking lights!). That can yield either of the
above symptoms, seemingly at random.

Diddling with Refresh didn't help. Because the update
does dominate I am trying to avoid Refresh and Invalidate
as much as possible.

Gary
 
Gary,

If I had to guess, you are performing all of your calculations in a loop
on the main UI thread, right? If this is the case, then what happens is
that while you are looping and performing your calculations, no other
messages can be processed, including the paint messages.

Because of this, you should perform your calculations on another thread,
and then call the Invoke method on a form/control on the main thread,
passing a delegate which will update the UI appropriately. This will allow
the UI to refresh itself appropriately, as it isn't tied up in the loop
performing computations.
 
I have a computation intensive application that tries to
update the display quite often. After a few seconds the
window stops updating and the window goes blank. A
breakpoint in the Paint event doesn't trigger.

Well, leaving aside the question of why anything that could be practically
executed on a PDP-1 should take any significant time when simulated on a
modern PC...

The issue you're having is because you are doing your calculations on the
main GUI thread. Presumably in response to some user interaction, like
clicking a button or choosing a menu command or the like. Until you
return from handling tha user interaction, no more messages can be
processed, including any WM_PAINT messages (which translate into the Paint
event).

The best way to address the issue is to use a different thread for your
processing. The BackgroundWorker class provides a very nice, convenient
way to do this, though of course there are other ways to implement it.
With the BackgroundWorker class, you handle a couple of events: one to
actually do the work, and one to signal the completion of the work. Then
you tell the BackgroundWorker instance to do the work. The delegates
assigned to the events are run on a different thread from your main GUI
thread, allowing normal window processing to occur.

You can even use Form.Invoke() from the worker thread to cause changes to
your displayed form. To cause simulated panel lights to turn on and off
as the state of the simulated computer changes, for example. :)
[...]
Diddling with Refresh didn't help. Because the update
does dominate I am trying to avoid Refresh and Invalidate
as much as possible.

I'm not sure why calling Refresh() doesn't update the window. If you call
it on the main form, it should. However, it's not a very good way to
address the issue anyway (nor is using the DoEvents() method, which allows
messages to be processed at intermediate points in time within some other
code, even though it would probably get the job done). So just as well
you couldn't get it to work. :)

Pete
 
I have a computation intensive application that tries to
Nicholas and Pete,

Thanks for you suggestions. I did two simple test cases, one of
basic threads and one with BackgroundWorker. The basic threads
case displayed the same blank screen behaviour as the simulation.
That might have been an error on my part. I've done multithreading
before but not in Windows. The BackgroundWorker case did
update the window properly.

However, the solution I did choose, for now at least, is a DoEvent
every 113 instructions. That handles paint and keyboard events
with little performance overhead or recoding. FWIW, the simulation's
release version runs 60 time faster than the original PDP-1 with the
lights off and 100 times more slowly with them on.

I will look more into why the basic threads case didn't work and into
doing a multithread version of the simulation.

Thanks again for your help.

Gary
 
[...] The BackgroundWorker case did
update the window properly.

However, the solution I did choose, for now at least, is a DoEvent
every 113 instructions.

I'm curious why, given that you were able to get the BackgroundWorker to
update the window as you wanted, did you instead choose what is generally
a bad idea to actually implement the solution? It seems to me that
BackgroundWorker is a very appropriate solution to the issue, while using
DoEvents() is not.

I'll refrain (except to say that I'm refraining) from commenting on what
sounds like extremely poor performance on the part of the simulation
itself. :)

Pete
 
Gary said:
Nicholas and Pete,

Thanks for you suggestions. I did two simple test cases, one of
basic threads and one with BackgroundWorker. The basic threads
case displayed the same blank screen behaviour as the simulation.
That might have been an error on my part. I've done multithreading
before but not in Windows. The BackgroundWorker case did
update the window properly.

However, the solution I did choose, for now at least, is a DoEvent
every 113 instructions. That handles paint and keyboard events
with little performance overhead or recoding. FWIW, the simulation's
release version runs 60 time faster than the original PDP-1 with the
lights off and 100 times more slowly with them on.

I will look more into why the basic threads case didn't work and into
doing a multithread version of the simulation.

Thanks again for your help.

Gary

Trigger the updates based on time instead. There is no reason to update
the screen more often than 100 times a second, as no monitor can show
updates more often than that. You will even get a good result with a lot
lower frequency than that.

As a comparison; cinema film updates at 24 frames per second. Animated
moves usually show each animation for two frames, so they update at 12
frames per second.
 
I'm curious why, given that you were able to get the BackgroundWorker to
update the window as you wanted, did you instead choose what is generally
a bad idea to actually implement the solution? <snip> DoEvents()

The threaded and BackgroundWorker test cases were independent of the
simulation. I did a DoEvents loop for comparison. I could thus take
metrics. I found that the interthread method call overhead exceeded
the DoEvent overhead. FWIW, the time costs were

draw a light >> interthread call overhead > DoEvent >> simulated
instruction

This likely is not what you expected. The DoEvent option had the advantages
that:

It did what I needed
It was the simplest solution - adding one line
And according to my metrics was a bit faster.
I'll refrain (except to say that I'm refraining) from commenting on what
sounds like extremely poor performance on the part of the simulation
itself. :)

You may. :-) Actually the instruction simulation is much too fast given
that I want to match the 5us cycle of the original. Space War, one target
application, requires some means of timing. Simulating the 110 blinking
lights in real time is not possible with current CPUs - 10x was the best
for a single light. As Goran pointed out the lights needn't be done on a
per cycle basis - that just makes it more realistic. (I'm doing this just
for fun - I don't have to be reasonable.)

The ultimate objective is to put it in hardware.

Gary
 
The threaded and BackgroundWorker test cases were independent of the
simulation. I did a DoEvents loop for comparison. I could thus take
metrics. I found that the interthread method call overhead exceeded
the DoEvent overhead.

IMHO, performance should not be an issue (either mechanism should be
plenty fast) and so the criteria should be what the "correct" way to do it
is.

Using DoEvents(), you are quantizing the UI's ability to process events,
whereas putting the simulation into a different thread and using Invoke()
allows the UI to always be ready to process events, while still responding
in a timely manner to the simulation. You could even use BeginInvoke() to
allow the simulation to continue processing unimpeded until the point in
time when its thread would normally get interrupted anyway.
[...]
The ultimate objective is to put it in hardware.

You are intending to build a PDP-1 replica?

I hope your project works...I think it would be really fun to have a
working PDP-1. :)

Pete
 
Back
Top