The GUI has several modeless dialogs, used to display the progress of
the calculations. The worker thread updates progress variables in
memory accessible to the GUI thread, and the GUI thread is notified
when these change.
The problem I am experiencing is that the modeless dialogs don't
redraw when the GUI updates the controls in the modeless dialogs.
What happens in the GUI thread is
Draw in controls
pModeless->pControl.Invalidate();
* see below
* If there is nothing here, the modeless dialog's controls are
repainted most of the time, but not always. If I put pModeless-
>UpdateWindow() here, the controls are not repainted at all.
I can't understand why adding UpdateWindow makes the modeless dialog
be repainted less often.
The control in question is a static window that has a bitmap about 50
x 100 pixels. It is possible that the control is erased, but for some
reason doesn't finish its repaint; that would give the observed
appearance.
How can I guarantee that, upon notification to the GUI thread that the
progress variables have changed, and I regenerate the bitmap to be
displayed, that it will either be completely redrawn (preferable), or
not drawn at all, leaving the previous image intact.
What if you use:
pModeless->pControl.UpdateWindow();
What else does your GUI thread do after that - are you not getting
back to processing the message loop?
You mention that repainting occurs most of the time, but not always -
what happens if you use a different graphics card (ATI/NVidia) or drop
the graphics acceleration settings or resolution to a std VGA mode?
Dave
No painting at all, same as pModeless->UpdateWindow().
> What else does your GUI thread do after that - are you not getting
> back to processing the message loop?
The GUI thread his repainting the main frame, no problem.
> You mention that repainting occurs most of the time, but not always -
> what happens if you use a different graphics card (ATI/NVidia) or drop
> the graphics acceleration settings or resolution to a std VGA mode?
The video card is NVidia GeForce 6200., running at 1680x1050, 60 Hz,
32-bit color. Full hw acceleration is being used.
I don't think it's the graphics card, because the main frame is full-
screen on 1680x1050, and it is redrawn every few seconds at the same
time as the modeless is not redrawn at all (with the added
UpdateWindow).
Setting the graphics acceleration to None doesn't change the result;
still no redrawing when pModeless->pControl.UpdateWindow() is used.
I am completely stumped here.
I'm afraid I don't know what else to suggest.
Dave
Where you say "Draw in controls" - what are you doing?
You normally draw in response to the WM_PAINT message (which is what
you should get after the Invalidate), but as you're using a static
control to display a bitmap you don't actually need to do any drawing
(yourself).
Dave
>I have an app that does heavy calculations. It's multi-threaded, with
>a GUI thread and a worker thread, started from the GUI thread.
>
>The GUI has several modeless dialogs, used to display the progress of
>the calculations. The worker thread updates progress variables in
>memory accessible to the GUI thread, and the GUI thread is notified
>when these change.
****
Of course, by this you MUST mean "The worker thread notifies the GUI thread to update its
control values", rather than "The worker thread touches the controls itself to change
them"
If you are touching the controls from the worker thread, think of your fundamental design
as being erroneous. You MUST *ask* the GUI thread to update the controls by posting
messages to it to perform the updates, which it will then do.
****
>
>The problem I am experiencing is that the modeless dialogs don't
>redraw when the GUI updates the controls in the modeless dialogs.
****
This sentence is not comprehensible. "When the GUI updates the controls" is not a
meaningful statement. You are not defining what *you* mean by "the GUI". If you had said
"The GUI thread" it would make sense. And, of course, this also means that you have not,
under any imaginable circumstatnces, done a WaitFor... operation on the thread handle to
wait for a thread to finish; this blocks the GUI thread, and thus blocks all redrawing. A
design which does a WaitForSingleObject, for exmaple, is an erroneous design and must be
rewritten to eliminate that wait. Never, ever, block the main GUI thread waiting for a
thread to finish!
Andi, if you say "But I don't want the user to do..." for anything you don't want the user
to do, it is YOUR responsibiity to disable those menu items/toolbar items/controls while
the thread is running. You do NOT do this by blocking the thread, but by disabling the
controls. You re-enable the controls when the thread finishes. You must NEV ER block the
main GUI thread! From your description this is what is happening.
>
>What happens in the GUI thread is
>
>Draw in controls
>pModeless->pControl.Invalidate();
****
Since you have not said what the control is, or why you are invalidating it, or what
context this code is executed in, there is no way to tell what it should do, or even why
it exists.
Note that if you have a class
class CMyModelessDialog : public CDialog {
{
public:
void DoSomething();
...
}
and in your thread you call
mydialogptr->DoSomething();
that code is NOT executed in the GUI thread; it is executed in the worker thread. I have
found it a common misconception that people think that because a class instance of a
dialog (or similar object) is created by the main GUI thread, then necessarily all the
code in that class is executed in the main GUI thread; this was never true; a method is
executed in the thread that calls it. Therefore, the only way to get something to execute
in the main GUI thread is to use SendMessage or PostMessage to cause a context swap to the
main GUI thread, and SendMessage introduces a host of new problems. You Don't Want To Go
There. Instead, use PostMessage. It has fewer problems (but not zero problems; you can
"saturate the message queue" and block all redrawing if there are too many PostMessage
calls happening; see my essay on I/O Completion Ports for a workaround)..
So even if you are properly (from an architectural viewpoint) doing PostMessage calls, you
could be saturating the message queue and not seeing redrawing. Then you will have to use
my fix for avoiding message queue saturation.
*****
>* see below
>
>* If there is nothing here, the modeless dialog's controls are
>repainted most of the time, but not always. If I put pModeless-
>>UpdateWindow() here, the controls are not repainted at all.
>
>I can't understand why adding UpdateWindow makes the modeless dialog
>be repainted less often.
****
If the main thread was blocked, this would not work.
****
>
>The control in question is a static window that has a bitmap about 50
>x 100 pixels. It is possible that the control is erased, but for some
>reason doesn't finish its repaint; that would give the observed
>appearance.
>
>How can I guarantee that, upon notification to the GUI thread that the
>progress variables have changed, and I regenerate the bitmap to be
>displayed, that it will either be completely redrawn (preferable), or
>not drawn at all, leaving the previous image intact.
*****
You will need to specify precisely what you are doing by answering the above questions.
joe
****
Joseph M. Newcomer [MVP]
email: newc...@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
>On Jul 6, 12:08 am, David Lowndes <Dav...@example.invalid> wrote:
>> What if you use:
>>
>> pModeless->pControl.UpdateWindow();
>
>No painting at all, same as pModeless->UpdateWindow().
****
Of course, THIS will in NO WAY cause any controls to be repainted, just the background of
the dialog! So it would be totally and completely useless, unless you have a background
image you are painting on the dialog.
*****
>
>> What else does your GUI thread do after that - are you not getting
>> back to processing the message loop?
>
>The GUI thread his repainting the main frame, no problem.
>
>> You mention that repainting occurs most of the time, but not always -
>> what happens if you use a different graphics card (ATI/NVidia) or drop
>> the graphics acceleration settings or resolution to a std VGA mode?
>
>The video card is NVidia GeForce 6200., running at 1680x1050, 60 Hz,
>32-bit color. Full hw acceleration is being used.
>
>I don't think it's the graphics card, because the main frame is full-
>screen on 1680x1050, and it is redrawn every few seconds at the same
>time as the modeless is not redrawn at all (with the added
>UpdateWindow).
>
>Setting the graphics acceleration to None doesn't change the result;
>still no redrawing when pModeless->pControl.UpdateWindow() is used.
>
>I am completely stumped here.
*****
How many PostMessages do you send? Do you check the result of PostMessage to see that it
has sent the information?
The worker thread does computations, and when certain points in those
are reached, the modeless dialog needs to be redone.
The worker then recomputes a pair of bitmaps and invalidates the
client rect of the control to be redrawn. That control is a class
based on a static control.
The GUI thread is definitely running, as evidenced by the fact that
the main frame is being repainted, even while the modeless dialogs are
not. You can also force repainting of the modeless dialogs by moving
them off-screen and then back.
There are no PostMessages or SendMesssages being used. I re-examined
the code, and found that it is the worker thread that is doing the
bitmap re-computation and the invalidation of the control. When I
added the UpdateWindow for either the modeless dialog or the control
in that dialog, this was also executed by the worker thread. Perhaps
this is the problem.
Yes, this is what you are doing wrong. The worker thread must not touch the GUI
directly. It should use PostMessage() or SendMessage() to instruct the main
thread to update the GUI.
--
David Wilkinson
I will try making that change tomorrow, and report the results.
>In more detail, here is what happens:
>
>The worker thread does computations, and when certain points in those
>are reached, the modeless dialog needs to be redone.
>
>The worker then recomputes a pair of bitmaps and invalidates the
>client rect of the control to be redrawn. That control is a class
>based on a static control.
****
This is the deep and fundamental error. The worker thread MUST NOT TOUCH the windows
owned by the GUI thread! The ONLY thing it should do is PostMessage a request to the main
GUI thread to do something to the windows!
*****
>
>The GUI thread is definitely running, as evidenced by the fact that
>the main frame is being repainted, even while the modeless dialogs are
>not. You can also force repainting of the modeless dialogs by moving
>them off-screen and then back.
****
Basic rule: Thou Shalt Not Touch A Window From Other Than The Thread That Owns It.
Secondary rule: There Shall Be Only One Thread Per Application That Owns All The Windows.
Thou Shalt Not Create Windows In Secondary Threads
*****
>
>There are no PostMessages or SendMesssages being used.
****
That's your major failure. You have to change this!
****
>I re-examined
>the code, and found that it is the worker thread that is doing the
>bitmap re-computation and the invalidation of the control. When I
>added the UpdateWindow for either the modeless dialog or the control
>in that dialog, this was also executed by the worker thread. Perhaps
>this is the problem.
****
See above rules.
My understanding is that what you mean by "owning a window" is who is
going to be responsible for painting it. I don't see why, say,
changing the contents of a control cannot be done by any thread. What
I have gotten from the discussion so far is the PAINT messages aren't
being routed to the GUI thread, which will paint them.
In other words, I think your rules are sufficient, and following them
is an easy way to avoid trouble, but I don't see that they're
necessary if (and this is a big if) you understand how the messaging/
painting system works.
As I said, I will try having the GUI thread issue the PAINT messages
(via UpdateWindow) and see if that makes everything work. It may also
be necessary to have the InvalidateRect in the GUI thread, although if
one of these is lost we should just see the previous contents of the
controls.
It shouldn't be done because those operations often invoke Windows
messages, which are inextricably tied to the thread that created the
window.
A good rule of thumb - never do anything in a worker thread to
directly affect the UI thread, other than post messages to the window
to have it update itself.
Dave
>On Jul 6, 3:53�pm, Joseph M. Newcomer <newco...@flounder.com> wrote:
>> This is the deep and fundamental error. �The worker thread MUST NOT TOUCH the windows
>> owned by the GUI thread! �The ONLY thing it should do is PostMessage a request to the main
>> GUI thread to do something to the windows!
>
>My understanding is that what you mean by "owning a window" is who is
>going to be responsible for painting it. I don't see why, say,
>changing the contents of a control cannot be done by any thread. What
>I have gotten from the discussion so far is the PAINT messages aren't
>being routed to the GUI thread, which will paint them.
****
No, by "owning a window" I mean: the thread that creates the window owns the window. All
messages to that window appear in the thread queue of that thread. That is the only
thread that should do ANYTHING to the window (note that PostMessage doesn't do things to
the window, it does things to the thread queue of the thread that owns the window, and
that's OK)
It works like this: acting on a window causes a context swap to the thread that owns the
window. If that thread is doing something else, the context swap blocks until it can be
done. This stops your thread, and if there are mutually dependent conditions (and all too
frequently, there are), your application deadlocks. We have a simple rule for dealing
wtih this: Don't Do It.
Bottom line: don't touch a window from any thread other than its owner thread. Whatever
problems you have will not receive sympathetic answers if you do this; you will be told
"fix the fundamental problem" and that will be the extent of the help you get. So: fix
the fundamental problem!
****
>
>In other words, I think your rules are sufficient, and following them
>is an easy way to avoid trouble, but I don't see that they're
>necessary if (and this is a big if) you understand how the messaging/
>painting system works.
****
The are necessary to avoid getting into trouble.
Here's a hint: those of us who are Windows OS/C++/MFC MVPs actually *understand* the very
complex rules that govern cross-thread access (at least, all the MVPs in these areas that
I've had interaction with know them). Our own personal solutions are: don't do that. The
way to tell a beginner from an expert is an expert knows the problems and avoids ever
facing them; beginner either doesn't know the problems or believes there is a solution
that will work. Back when I was lerning to fly a plane, the adage was "A superior pilot
is the sort of person who employs his superior judgment to avoid situations in which his
superior skills are required to save the plane". So if the *experts* who *know* the rules
they have to follow avoid doing it, doesn't it suggest that we know that eventually this
will get us into deep trouble? So why do you think we give the rule about don't do
cross-thread window manipulation?
****
>
>As I said, I will try having the GUI thread issue the PAINT messages
>(via UpdateWindow) and see if that makes everything work. It may also
>be necessary to have the InvalidateRect in the GUI thread, although if
>one of these is lost we should just see the previous contents of the
>controls.
****
As long as you are manipulating the windows from another thread, you will keep running
into unresolvable problems because of it. So nobody is suprised in the slightest that you
are having problems, because you are violating the simple rule: don't do that. And if we
are faced with these problems, we change our code so we don't do that. I get code from
clients that is non-working, I modify it to not do cross-thread manipulation, and all the
problems go away. I don't bother to work out WHY their code was failing, down to
precisely which of the arcane rules they were breaking; instead, I brute-force make the
problem go away. This is the key to success. Don't do things which are known to fail,
even if you understand all the bizarre rules that have to be followed. They will break
under maintenance (back when I was unaware of these rules, about 12 years ago, I made this
mistake; it cost me real money to find and fix it, and I never, ever made that mistake
again. In fact, I had accidentally managed to obey all the arcane rules, but one tiny
change in what I was doing broke code that had been working for months. So I learned:
don't do that).
Here are more details:
I have a class (derived from CStatic) that contains a bitmap image. I
have a modeless dialog that contains a control which is an instance of
this class; this is the window that needs periodic updating. The class
also contains an OnPaint handler, which paints the bitmaps to the
control.
The worker thread is accessing members of the class which cause
recomputation of the bitmaps. It is then invalidating the client rect
of the control.
When I added an UpdateWindow call, either of the control or the
modeless dialog, to the worker thread, I got blank control all the
time.
So, which of these must be done by the window's owner, the GUI thread?
Update: I discovered that I was blanking the control multiple times
unnecessarily, and when I removed that, everything worked as expected,
without changing the worker thread's controlling the window
repainting.
However, I would like to understand your rules. I understand the
philosophy of staying away from trouble by doing things in the
approved manner. It's just that with MS docs, it's hard to determine
what that is.
>OK, let's say I want to follow your rules. I need to know what
>qualifies as manipulating or touching the windows.
****
Any operation which uses the HWND as its input, except ::PostMessage, or any method of
CWnd::, OTHER than CWnd::PostMessage. ALL other operations are forbidden to other than
the owner thread. Note that CWnd::PostMessage is just ::PostMessage(m_hWnd) so they are
considered identical for all practical purposes. In fact, ALL methods of CWnd turn into
operations that use the HWND stored in m_hWnd.
***
>
>Here are more details:
>
>I have a class (derived from CStatic) that contains a bitmap image. I
>have a modeless dialog that contains a control which is an instance of
>this class; this is the window that needs periodic updating. The class
>also contains an OnPaint handler, which paints the bitmaps to the
>control.
>
>The worker thread is accessing members of the class which cause
>recomputation of the bitmaps. It is then invalidating the client rect
>of the control.
*****
See above rule. Invalidate() violates it. So does UpdateWindow.
****
>
>When I added an UpdateWindow call, either of the control or the
>modeless dialog, to the worker thread, I got blank control all the
>time.
****
My reaction: yeah, it doesn't work. I am not surprised in the slightest. This is the
reason for the rules about cross-thread window manipulation. I would not even attempt to
figure out what is going wrong, I would just change the code to avoid cross-thread window
manipulation.
****
>
>So, which of these must be done by the window's owner, the GUI thread?
****
See the above rule. The only permissible operation on an HWND is ::PostMessage
****
>
>Update: I discovered that I was blanking the control multiple times
>unnecessarily, and when I removed that, everything worked as expected,
>without changing the worker thread's controlling the window
>repainting.
****
It is just an accident. A catastrophe is lurking, waiting to happen. Don't worry, it
will happen. Probably an hour before the final build before product release, or six
months after, to your most important user.
****
>
>However, I would like to understand your rules. I understand the
>philosophy of staying away from trouble by doing things in the
>approved manner. It's just that with MS docs, it's hard to determine
>what that is.
****
See above rule. If it uses an HWND, and it is not PostMessage, it is illegal.
joe
*****