Add BOOL m_bNeedUpdateControls member variable (initialize to TRUE).
Call NeedUpdateControls() as necessary. This code works for both modal and
non-modal.
dialogs that need UI update functionality.
"Joseph M. Newcomer" <newcomer@flounder.com> wrote in message
See below...
On Fri, 7 Mar 2008 12:01:06 +0100, "Giovanni Dicanio"
<giovanni.dicanio@invalid.com>
wrote:
"Joseph M. Newcomer" <newcomer@flounder.com> ha scritto nel messaggio
news:8bj0t35tp2t3p9sdfbdc7b3sohjhjontcb@4ax.com...
Note that if you want to flush messages, the concept of flushing
messages
is not related
to thread lifetime, but to logical operations outside the thread-thread
communication. For
example, when the mass spectrometer is driving out massive amounts of
trace data, it can
have several thousand messages queued. But the concept of "stop
tracing"
is not related
to the thread or its logic; those messages are valid messages. But the
*user* wants to
see the messages stop, and from the user's viewpoint, stopping the
message
stream by
discarding all queued messages is a concept of the receiver, not the
sender. So I set a
flag, and when a message is dequeued, I just discard it (and that means
freeing up its
storage). No problem.
OK, I've done something like that (with a flag).
Basically, I have a background worker thread that does long computations
and
sends a custom message (we can call it WMU_WORKER_PROGRESS) to the main
GUI
thread, after each computation iteration is done.
When the computation is completed, the background worker thread sends
another custom messge (WMU_WORKER_COMPLETED) to the main GUI thread.
The progress message (WMU_WORKER_PROGRESS) has a pointer to some data in
LPARAM. This data is dynamically allocated on the heap using 'new' by the
sender (the worker thread), and it is deleted by the receiver (the GUI
thread) with 'delete'.
Something like this:
DWORD SomeClass::WorkerThreadProc()
{
...
bool running = true;
while ( running )
{
// Stop thread if requested
// ('cancelRequest' is a bool)
if ( cancelRequest )
{
// Stop thread loop
running = false;
continue; // will exit while
}
// Stop loop if computation is completed,
// and send completion message to the main GUI thread (associated
// with 'window' HWND handle)
if ( ...computation completed... )
{
::PostMessage( window, WMU_WORKER_COMPLETED, 0, 0 );
// Stop thread loop
running = false;
continue;
}
// Do thread job for this iteration
...
...
// Report progress to main GUI thread.
// In the progress message, some data is dynamically allocated
// and the pointer passed in LPARAM.
// The receiver (main GUI thread) will delete the pointer
ProgressMsgData * data = new ProgressMsgData();
... set data fields (like percentage of job currently done) ...
::PostMessage( window, WMU_WORKER_PROGRESS, 0, (LPARAM)data );
}
return 0;
}
In the GUI thread, I have message handlers for WMU_WORKER_COMPLETED and
WMU_WORKER_PROGRESS.
Everything works fine, but there is a problem in only one case:
If the dialog-box (the GUI thread) is closed, sometimes some messages are
leaked!
****
Yes, that would be the expected behavior.
****
I mean, the dynamically allocated memory is leaked (even if the VS2008
does
not signal that).
I do some tracing from the custom message class constructors and
destructors, and, analyzing the tracing log, I see that sometimes the
destructor calls do not match the constructor calls (i.e. it sometimes
happens that a destructor is not called, so heap memory is leaked).
*****
Yes, that would be the expected behavior
*****
I think that this is becasue, if the user requests dialog closing
(clicking
the 'X' button in the upper right corner of dialog-box), it may happen
that
Windows put the WM_COMMAND with IDCANCEL message in the message queue, but
also the background worker thread sends some more WMU_WORKER... messages
in
the queue (before closing).
So, when I process the IDCANCEL, I have some pending WMU_WORKER...
messages
that I don't process (and so the heap memory is leaked).
****
Yes, that would be the expected behavior
****
(This does not happen always, it happens sometimes - this
non-deterministic
nature of multi-threading makes multi-threading debugging and testing
difficult.)
If I had a garbage collector, I would have no problems, in fact the GC
would
think about deleting the data associated to unprocessedd WMU_WORKER...
messages.
But how can I discard this data manaully?
If I receive the IDCANCEL, how can I process all the pending (if any)
WMU_WORKER... messages, just to delete the data pointed in LPARAM?
****
You need to deal with the issue of how controls are enabled/disabled in
your dialog. See
my essay on Dialog Control Management. I centralize all this in a single
function called
updateControls().
Typically, I have complex rules for enabling/disabling controls, so I need
to implement
rules for many (and sometimes) all controls. If you do no conditional
enabling, you could
simplify this considerably.
To show how I would incorporate thread shutdown information, I need to
show the
constructor and the OnOK/OnCancel handlers.
CMyDialog::CMyDialog() {
ClosePending = 0;
ThreadRunning = FALSE;
}
void CMyDialog::OnOK()
{
if(ThreadRunning)
{
cancelRequest = TRUE;
ClosePending = IDOK;
updateControls();
return;
}
CDialog::OnOK();
}
void CMyDialog::OnCancel()
{
if(ThreadRunning)
{
cancelRequest = TRUE;
ClosePending = IDCANCEL;
updateControls();
return;
}
CDialog::OnCancel();
}
In order to start up the thread, I would set the variables the thread
relies on before
starting the thread, and I would pass a parameter into the thread
function. Note that
these two variables are member variables of the class, not static
variables and not not
global variables.
...
ThreadRunning = TRUE;
cancelRequest = FALSE;
AfxBeginThread(threadfunc, this);
...
The handler deals with the fact that a close may have been marked as
pending.
LRESULT CMyDialog::OnThreadFinished(WPARAM, LPARAM)
{
ThreadRunning = FALSE;
if(ClosePending != 0)
PostMessage(WM_COMMAND, MAKEWPARAM(ClosePending, 0));
return 0;
}
The code below is an example of what I might do if the user were filling
out a form. Note
the careful use of &= and |= to guarantee monotonicity and to avoid having
to write
gigantic boolean expressions that will probably be wrong
void CMyDialog::updateControls()
{
BOOL masterEnable = ClosePending == 0;
CString s;
BOOK ok = TRUE; //assume OK button should be enabled
c_Cancel.EnableWindow(masterEnable);
c_Name.EnableWindow(masterEnable);
c_Name.GetWIndowText(s);
s.Trim();
ok &= !s.IsEmpty(); // cannot close if name is blank
// Note that the "Your role in company" must have at least one
// box checked
BOOL AnyRoleChecked = FALSE;
AnyRoleChecked |= c_Officer.GetCheck() == BST_CHECKED;
AnyRoleChecked |= c_Programmer.GetCheck() == BST_CHECKED;
AnyRoleChecked |= c_Janitor.GetCheck() == BST_CHECKED;
ok &= AnyRoleChecked;
c_OK.EnableWindow(masterEnable && ok);
}
If you are doing no enabling, you could do this more simply by
for(CWnd * wnd = GetWindow(GW_CHILD); wnd != NULL; wnd =
wnd->GetWindow(GW_HWNDNEXT))
wnd->EnableWindow(ClosePending == 0);
Now I'm curious why you are using ::PostMessage and where the HWND
'window' is defined. It
must not be a global or static variable, because usage of a static or
global varable is
not justified here. You seem to indicate the thread function takes no
parameters, but
that is incorrect, because it always takes an LPVOID. I would see the
function as being
as defined below (note that I always include 'static' as a comment in my
implementation of
static methods)
/* static */ DWORD CMyDialog::ThreadFunc(LPVOID p)
{
CMyDialog * wnd = (CMyDialog *)p;
...
while(running)
{ /* thread loop */
if(wnd->cancelRequest)
break;
...
wnd->PostMessage(WMU_WORKER_PROGRESS, (WPARAM)data);
...
} /* thread loop */
wnd->PostMessage(WMU_WORKER_COMPLETED);
return 0;
}
The thread function should involve *no* global or static variables unless
there is some
compelling reason to do so, and there is not even a mild reason to use
them here. Any
context it needs will come in via that LPVOID.
joe
****
Thanks very much,
Giovanni
Joseph M. Newcomer [MVP]
email: newcomer@flounder.com
Web: http://www.flounder.com
MVP Tips: http://www.flounder.com/mvp_tips.htm