MFC UI Threads, OnIdle, and Temp Map Headaches

已查看 128 次
跳至第一个未读帖子

Jay Daniel

未读,
2006年5月12日 09:18:362006/5/12
收件人
I'm hoping someone can shed some light on some difficulties I've been
having with an MFC application I'm writing. I think I've solved my
problem, but I'm wary since it's one of those "I'm not sure why I have
to do this" solutions that generally mean one doesn't fully understand
the problem.

My application is written using VS2005 and MFC 8.0 and can be
compiled either for the desktop our (our ultimate target) WinCE 5.0.
In addition to the "main" application thread (which is just responsible
for starting/ending threads in the application), we have several other
CWinThread-derived threads--one of which is responsible for creating
the UI for the application. I started wondering where I'd gone wrong
when I noticed that over time as our application ran, it would slow
down until it eventually hung (although it would often recover if given
about 5 minutes to get its whits about it). Eventually, I came up with
the theory that it was somehow my GDI calls that were slowing things
down. It seems I was pretty close, but it's actually the temporary
objects being created with a smattering of whatever::FromHandle() calls
that were growing the threads tempMap to huge sizes.

The upshot of all this is that I noticed that the section of
CWinThread::OnIdle that would be responsible for deleting the temporary
objects was never being called. That noticed, I "solved" the problem
by adding a call to OnIdle(1) every few minutes in the application.
This is just a temporay kludge to prove my point, but I'd like to know
how I should implement a proper solution. Taking a look at MFC's code,
however, I wonder if there's a bug. Let me explain.

Here's what CWinThread::Run() looks like:

for (;;)
{
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE))
{
// call OnIdle while in bIdle state
if (!OnIdle(lIdleCount++))
bIdle = FALSE; // assume "no idle" state
}

// phase2: pump messages while available
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();

// reset "no idle" state after pumping "normal" message
//if (IsIdleMessage(&m_msgCur))
if (IsIdleMessage(&(pState->m_msgCur)))
{
bIdle = TRUE;
lIdleCount = 0;
}

} while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL,
PM_NOREMOVE));
}


So if I'm understanding things correctly, it'll call OnIdle(0) to
start, then increment lIdleCount to 1. Then, if CWinThread::OnIdle had
indicated there was more to be done (and this is the stick point),
it'll call it again with OnIdle(1). HOWEVER, taking a look at
CWinThread::OnIdle() I see this:

if (lCount <= 0)
{
/* Send WM_IDLEUPDATECMDUI to pMainWnd and all it's children as well
as all registered Frame Windows */
}
else if (lCount >= 0)
{
AFX_MODULE_THREAD_STATE* pState =
_AFX_CMDTARGET_GETSTATE()->m_thread;
if (pState->m_nTempMapLock == 0)
{
// free temp maps, OLE DLLs, etc.
AfxLockTempMaps();
AfxUnlockTempMaps();
}
}

return lCount < 0; // nothing more to do if lCount >= 0


SO... CWinThread::OnIdle(0) will ALWAYS return FALSE to indicate that
it has nothing more to do, despite the fact that the second portion
(which cleans up the TempMaps) has never been executed. Is this a bug?
I ask only because the construct is:

if (lCount <= 0)
else if (lCount >=0)

which doesn't make any sense. Logically, they could just have "else
(lCount > 0)" since the mere presence of "else" and the preceding block
will preclude hitting that section if lCount == 0. Another possibility
for this being a bug would have been if they had intended to NOT have
the else at all (thus an lCount < 0 executes block 1, lCount == 0
executes both, and lCount > 0 only executes block 2). I've digressed a
bit, but I do wonder whether this is a mistake in MFC.

Moving on, I can see that CWinApp rectifies this situation by
overriding OnIdle as follows:

if (lCount <= 0)
{
CWinThread::OnIdle(lCount);

// call doc-template idle hook
POSITION pos = NULL;
if (m_pDocManager != NULL)
pos = m_pDocManager->GetFirstDocTemplatePosition();

while (pos != NULL)
{
CDocTemplate* pTemplate = m_pDocManager->GetNextDocTemplate(pos);
ASSERT_KINDOF(CDocTemplate, pTemplate);
pTemplate->OnIdle();
}
}
else if (lCount == 1)
{
VERIFY(!CWinThread::OnIdle(lCount));
}
return lCount < 1; // more to do if lCount < 1


So, CWinApp's version calls OnIdle for any registered Document
templates, and then says "I have more to do" so that it can call
CWinThread::OnIdle(1) and clean up the temp maps. I'm not sure what
this discovery tells me about how MFC is supposed to be used, but maybe
someone could clarify for me. The following are the options I'm able
to come up with:

1. Applications should always use the main CWinApp-derived thread as
the one that creates any UI elements.

2. CWinThread-derived UI threads that create windows objects are
responsible for making sure the tempMaps get cleaned up on their own.

Could someone tell me if I've completely missed the mark or if any of
this makes sense?

Best Regards,

Jay Daniel
Phaze42...@gmail.com

Ajay Kalra

未读,
2006年5月12日 09:42:332006/5/12
收件人
Why do you see the need to have multiple UI threads? How are you using
it. Typically you should have a window and its owner window in the same
thread.

--
Ajay

Jay Daniel

未读,
2006年5月12日 09:53:022006/5/12
收件人
Ajay,

All of the windows (with the exclusion of the "main" application window
which is hidden DO belong to the same thread). I used the terminology a bit
confusingly. MFC refers to any thread with a message queue as a "UI Thread"
(as opposed to a "worker" thread with just a thread function). I this case,
I have several "UI threads" in the MFC/windows sense of the word, but only
ONE of them is actually displaying windows and interacting with the
user/display. My apologies for being confusing.

-Jay Daniel
pHaze42...@gmail.com


--
Jay Daniel

Doug Harrison [MVP]

未读,
2006年5月12日 11:00:432006/5/12
收件人
FWIW, your analysis makes sense to me. I've noticed the "<= 0" and ">= 0"
if/else tests (mistakes) in CWinThread::OnIdle before but never related
them to the CWinApp override. You should go here and submit a bug report:

http://lab.msdn.microsoft.com/productfeedback/Default.aspx

If you do, let us know the URL for the bug report.

--
Doug Harrison
Visual C++ MVP

On 12 May 2006 06:18:36 -0700, "Jay Daniel" <Phaze42...@gmail.com>
wrote:

Jay Daniel

未读,
2006年5月12日 11:32:022006/5/12
收件人
Doug,

Thanks for the reply. The bug submission can be found here:

http://lab.msdn.microsoft.com/ProductFeedback/viewFeedback.aspx?feedbackId=FDBK49881

It's necessarily a bit more terse than my rambling post, but hopefully
conveys the idea. If you think I've missed anything important there, perhaps
you could supplement it with a comment (and/or vote for its importance so
someone at MS actually has a chance of seeing it).

-Jay Daniel
pHaze42...@gmail.com

--
Jay Daniel

Doug Harrison [MVP]

未读,
2006年5月14日 18:19:252006/5/14
收件人
On Fri, 12 May 2006 08:32:02 -0700, Jay Daniel
<JayD...@newsgroups.nospam> wrote:

>Doug,
>
> Thanks for the reply. The bug submission can be found here:
>
>http://lab.msdn.microsoft.com/ProductFeedback/viewFeedback.aspx?feedbackId=FDBK49881
>
> It's necessarily a bit more terse than my rambling post, but hopefully
>conveys the idea. If you think I've missed anything important there, perhaps
>you could supplement it with a comment (and/or vote for its importance so
>someone at MS actually has a chance of seeing it).

Your summary is fine, and the bug is currently "under review". I did
validate it and vote on it, the former being a prerequisite for the latter.

Fei Xu

未读,
2006年7月8日 13:14:012006/7/8
收件人
Yes, Jay, I have the same confusion when I read the MFC source code.
I think CWinThread::OnIdle() has a bug but it's fixed by CWinApp::OnIdle()

:)

回复全部
回复作者
转发
0 个新帖子