I have a class based on a standard CDialog, in it I have a thread
which loops every 100 ms. This thread has two purposes;
1) it invalidates a painting area, forcing the dialog to redraw even
if windows don't see it as nessesary
2) it updates values in various labels and such
My problem is with #2, and specifically when the app is about to exit.
At OnDestroy I have to stop the thread, as after OnDestroy has
finished the hWnds to all child labels are not usable anymore. To do
this I wait in OnDestroy for the thread to finish its current cycle
(using a semaphore), and then stop it. The problem is that writing
data to the labels is essentially sending a message to the message
queue, and the message queue is at the moment stopped in OnDestroyed
(I think), thus a deadlock! I can't for the life of me figure out how
to reliably stop the thread without this deadlock occuring.
"andreas.z...@gmail.com" wrote:
Maybe I'm misunderstanding what you're doing. I wouldn't use a thread for
this purpose. I'd set up a timer (SetTimer(1,100,NULL))and do the updates in
OnTimer().
Move the code to destroy the thread earlier in the lifetime of the dialog.
OnDestroy is too late. Perhaps OnClose, but I'm not sure if this is called
for a dialog. If not, perhaps override OnOK and OnCancel.
-- David
@Stephen Myers
Yes I could use a timer instead, but doesn't a timer also work by
sending messages on the queue? Won't I be equally deadlocked only it
would be OnTimer that locked me instead of OnDestroy? But maybe it
doesn't work like that. What happens if you send messages "from a
message (OnTimer)"? If that works this could be the solution, as
OnTimer never can execute at the same time as OnDestroy and they would
be automatically mutually exclusive.
@David Ching
I don't actually use OnOK and OnCancel, it's a CDialog at base, but
converted to work as a form for a CTabCtrl, so I have overridden OnOK
and OnCancel to do nothing. In any case, aren't OnOK and OnCancel also
responds to messages from the pump? In that case they would deadlock
in the same way as OnDestroy.
"andreas.z...@gmail.com" wrote:
> On 13 Feb, 18:39, "David Ching" <d...@remove-this.dcsoft.com> wrote:
> > <andreas.zetterst...@gmail.com> wrote in message
> >
> > news:39e33a6a-dbc7-466f...@z1g2000yqn.googlegroups.com...
> >
> >
> >
> > > Let's see if I can explain this...
> >
> > > I have a class based on a standard CDialog, in it I have a thread
> > > which loops every 100 ms. This thread has two purposes;
> > > 1) it invalidates a painting area, forcing the dialog to redraw even
> > > if windows don't see it as nessesary
> > > 2) it updates values in various labels and such
> >
> > > My problem is with #2, and specifically when the app is about to exit.
> >
> > > At OnDestroy I have to stop the thread, as after OnDestroy has
> > > finished the hWnds to all child labels are not usable anymore. To do
> > > this I wait in OnDestroy for the thread to finish its current cycle
> > > (using a semaphore), and then stop it. The problem is that writing
> > > data to the labels is essentially sending a message to the message
> > > queue, and the message queue is at the moment stopped in OnDestroyed
> > > (I think), thus a deadlock! I can't for the life of me figure out how
> > > to reliably stop the thread without this deadlock occuring.
> >
> > Move the code to destroy the thread earlier in the lifetime of the dialog..
> > OnDestroy is too late. Perhaps OnClose, but I'm not sure if this is called
> > for a dialog. If not, perhaps override OnOK and OnCancel.
> >
> > -- David
>
> @Stephen Myers
> Yes I could use a timer instead, but doesn't a timer also work by
> sending messages on the queue? Won't I be equally deadlocked only it
> would be OnTimer that locked me instead of OnDestroy? But maybe it
> doesn't work like that. What happens if you send messages "from a
> message (OnTimer)"? If that works this could be the solution, as
> OnTimer never can execute at the same time as OnDestroy and they would
> be automatically mutually exclusive.
>
> @David Ching
> I don't actually use OnOK and OnCancel, it's a CDialog at base, but
> converted to work as a form for a CTabCtrl, so I have overridden OnOK
> and OnCancel to do nothing. In any case, aren't OnOK and OnCancel also
> responds to messages from the pump? In that case they would deadlock
> in the same way as OnDestroy.
>
I use the timer technique heavily and have not seen a problem. There is not
a second thread to cause a deadlock.
Read my essay on worker threads on my MVP Tips site.
You are doing a cross-thread SendMessage, which should always be thought of as a Really
Bad Idea To Be Avoided At All Costs. Don't touch a window owned by one thread from any
other thread, period. Otherwise, the failure you see is inevitable.
Waiting in OnDestroy is a particularly poor way to deal with this, and must not be used.
You CANNOT make it work, so don't even bother trying. It is the wrong place, FAR too
late, to discover that the thread should be shut down. See my essay on worker threads.
You have to initation shutdown as a two-phase process
OnClose/OnOK/OnCancel:
if a thread is running
Tell the thread to stop
Set a flag saying you are in shutdown mode
return without calling the CDialog superclass method
thread:
When it sees it should stop, it exits its loop, then does a
PostMessage a user-defined completion message to the main thread
main thread OnThreadCompleted handler
indicate the thread has finished
if the shutdown mode flag is set, PostMessage(WM_CLOSE) to
your window to close
(Note that since there is no thread running, it will
shut down as expected)
The failure here is that you are trying to impose sequential semantics in an asynchronous
world. Generally, treat most WaitForSingleObject calls in the main thread as a design
error. You must not block the message pump. So make everything be asynchronous in the
first place, don't think about forcing waits, and the problem goes away. And NEVER
manipulate windows from another thread!
joe
Joseph M. Newcomer [MVP]
email: newc...@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm
On Fri, 13 Feb 2009 08:33:48 -0800 (PST), Seetharam <smi...@gmail.com> wrote:
>in OnDestroy(), can't you wait until your thread is done executing?
>
>-SM
****
And your point is? So what? First, OnTimer does *not* "send" a message to the queue; nor
does it "post" a message. What happens is that if the timer goes off, the next time
GetMessage discovers that there is nothing in the queue it simulates the receipt of a
timer message.
*****
>Won't I be equally deadlocked only it
>would be OnTimer that locked me instead of OnDestroy?
****
Impossible. It cannot deadlock, because you are not waiting for anything.
****
>But maybe it
>doesn't work like that. What happens if you send messages "from a
>message (OnTimer)"? If that works this could be the solution, as
>OnTimer never can execute at the same time as OnDestroy and they would
>be automatically mutually exclusive.
****
Yes. Point being that once DestroyWindow starts its cycle, the timer is killed. You
don't need a separate thread unless you have some lengthy computation you need to do each
time, and don't want to bog down the main thread and kill user responsiveness. Do not
confuse timer events with threads; they are not related in any way.
****
>
>@David Ching
>I don't actually use OnOK and OnCancel, it's a CDialog at base, but
>converted to work as a form for a CTabCtrl, so I have overridden OnOK
>and OnCancel to do nothing. In any case, aren't OnOK and OnCancel also
>responds to messages from the pump? In that case they would deadlock
>in the same way as OnDestroy.
****
You said you had a dialog. So either you have a dialog, or you have a property page. If
you had a property page, you should have said so.
YOu are hung up on the issue of deadlock because your fundamental approach is so deeply
flawed that it is unrecoverable. Redesign it (either use WM_TIMER or look at my thread
essay) and the problem becomes nonexistent, and therefore you do not need to worry about
it at all.
joe
****
1) I'm showing simple moving graphics with related data shown in
various CLabels and CListCtrls, which is why I need to update the
window more often than Windows wants me to.
2) I'm calling functions such as CListCtrl::InsertItem and
CStatic::SetWindowText which I assume at some level posts or sends a
message to the queue, waits for it to complete and hangs the app due
to me incorrectly waiting in OnDestroy.
Your method of stopping the thread from OnClose seems like a perfectly
good solution, and in fact I myself tried to do that, but by some
wierd reason OnClose is never called in my app. I have traced it down
to the main CFormView, and as it is part of the base of the MFC
project I don't know how I can get its OnClose to activate. That is
why I was using OnDestroy as it was the only message that actually was
reliably called. So I guess this is another obstacle I have to
overcome.
Just to clarify: My app is an MFC application, single document,
created by the wizard. In the main CFormView I create a CTabView
derived class, which itself then creates a number of tabs which are
realized by CDialog derived classes where the OnOk and OnCancel bodies
have been emptied, the buttons deleted and the class being defined as
a child (as I can see you yourself do in your examples). In OnClose in
CFormView I call CTabView::CloseWindow and in CTabView's OnClose I
call CloseWindow for the CDialogs. But as I never get the OnClose from
CFormView of course none of the others are called either.
About the closing of the thread (or timer as I guess it should be
instead); why do I have to post a user defined message and then when
receiveing that message posting WM_CLOSE? can't I post WM_CLOSE
directly from the thread/timer as it finishes for the last time?
------------
>****
>And your point is? So what? First, OnTimer does *not* "send" a message to the queue; nor
>does it "post" a message. What happens is that if the timer goes off, the next time
>GetMessage discovers that there is nothing in the queue it simulates the receipt of a
>timer message.
>*****
What I meant was, if OnTimer is run by the same thread that handles
the messages, won't it deadlock itself if I somewhere in the OnTimer
function tries to post a new message? as through CListCtrl::InsertItem
or CStatic::SetWindowText?
>You said you had a dialog. So either you have a dialog, or you have a property page. If
>you had a property page, you should have said so.
I don't really know what a property page is, but looking at this link
(http://msdn.microsoft.com/en-us/library/675f1588(VS.80).aspx) I'm
pretty sure a CDialog derived class with the OnOk and OnCancel bodies
emptied is much closer to a CDialog than a property page.
Thanks for the input. I'm definitely going to bookmark your tips page.
****
What does Windows wanting you to do the update have to do with anything. The normal
mechanism that you invoke some form of InvalidateRect (such as Invalidate()) whenever
*you* want to update something. Windows attitude on update does not get involved in this
at all. So presumably the reason you call Invalidate() is that *you* have something that
changed, which is not stated in your original description (you just said that it
invalidated every 100 milliseconds, but not that there was a reason for doing so)
****
>2) I'm calling functions such as CListCtrl::InsertItem and
>CStatic::SetWindowText which I assume at some level posts or sends a
>message to the queue, waits for it to complete and hangs the app due
>to me incorrectly waiting in OnDestroy.
****
Right. Which is why you would never, ever call CListCtrl::InsertItem or
CStatic::SetWindowText from a thread. Because of what you saw, it is dangerous to do so.
Deadlock is the usual error encountered. If you want to modify something, you PostMessage
to the main GUI thread and ask *it* to do the updates for you. You NEVER modify a
window's contents or take other actions on it from a thread that does not own it.
****
>
>Your method of stopping the thread from OnClose seems like a perfectly
>good solution, and in fact I myself tried to do that, but by some
>wierd reason OnClose is never called in my app. I have traced it down
>to the main CFormView, and as it is part of the base of the MFC
>project I don't know how I can get its OnClose to activate. That is
>why I was using OnDestroy as it was the only message that actually was
>reliably called. So I guess this is another obstacle I have to
>overcome.
****
Note that this applies only to top-level dialogs. You do not have a top-level dialog, so
instead it becomes an issue that when your top-level view receives an OnClose it has to
notify any child windows (such as your dialog in the tab control) that they are to shut
down. Because this is is in the same thread, you can use SendMessage, and SendMessage can
return a BOOL indicating if deferred shutdown is required.
****
>
>Just to clarify: My app is an MFC application, single document,
>created by the wizard. In the main CFormView I create a CTabView
>derived class, which itself then creates a number of tabs which are
>realized by CDialog derived classes where the OnOk and OnCancel bodies
>have been emptied, the buttons deleted and the class being defined as
>a child (as I can see you yourself do in your examples). In OnClose in
>CFormView I call CTabView::CloseWindow and in CTabView's OnClose I
>call CloseWindow for the CDialogs. But as I never get the OnClose from
>CFormView of course none of the others are called either.
****
Good clarification. But see above. The CFormView, when it receives a notification to
close, must notify the child windows that it is closing. Otherwise, the CFormView has to
"own" the thread so it knows how to shut it down and receives the shutdown notification.
****
>
>About the closing of the thread (or timer as I guess it should be
>instead); why do I have to post a user defined message and then when
>receiveing that message posting WM_CLOSE? can't I post WM_CLOSE
>directly from the thread/timer as it finishes for the last time?
****
Because you should not be controlling windows from other threads. Essentially, if it
involves anything but PostMessage, it can only be done from the thread that owns the
window.
Note that this presumes that your thread knows it is the only thread involved, and that
shutting down always implies a close of the app is required. The thread should not know
this. If you add another thread, each thread handles its shutdown by notifying its
"owner" that it has shut down; it is only the owner that decides that this implies a
postponed WM_CLOSE should be continued. This is because a thread might terminate for
other reasons, which would NOT require that the application close.
joe
****
>
>------------
>
>>****
>>And your point is? So what? First, OnTimer does *not* "send" a message to the queue; nor
>>does it "post" a message. What happens is that if the timer goes off, the next time
>>GetMessage discovers that there is nothing in the queue it simulates the receipt of a
>>timer message.
>>*****
>What I meant was, if OnTimer is run by the same thread that handles
>the messages, won't it deadlock itself if I somewhere in the OnTimer
>function tries to post a new message? as through CListCtrl::InsertItem
>or CStatic::SetWindowText?
>
>>You said you had a dialog. So either you have a dialog, or you have a property page. If
>>you had a property page, you should have said so.
>I don't really know what a property page is, but looking at this link
>(http://msdn.microsoft.com/en-us/library/675f1588(VS.80).aspx) I'm
>pretty sure a CDialog derived class with the OnOk and OnCancel bodies
>emptied is much closer to a CDialog than a property page.
>
>Thanks for the input. I'm definitely going to bookmark your tips page.
Joseph M. Newcomer [MVP]
email: newc...@flounder.com
For posterity:
I implemented a normal timer instead. I started it in OnInitDialog,
and stopped it in OnDestroy.
As the timer event is executed by the dialog itself it can never be
active at the same time
as OnDestroy and I can be sure it doesn't run any more loops after
OnDestroy has been called.
Thus I get syncronization automatically and simple.
Thanks for all the help.
****
For this to work, you have to make sure that SendMessage (within the same thread) is going
to the correct window. Also, for dialogs, you cannot return a value in your LRESULT; you
have to return the value by ::SetWindowLong(m_hWnd, DW_DLGRESULT, (LPARAM)result) because
of how dialogs work. Check the name of that field, because I am trusting my memory here.
****
Joseph M. Newcomer [MVP]
email: newc...@flounder.com