Windows的對(duì)話(huà)框是獲取信息輸入的主要手段,增加線(xiàn)程則是獲得更好UI響應(yīng)的重要方式。由于Windows在處理對(duì)話(huà)框時(shí)進(jìn)行線(xiàn)程調(diào)度的特殊性,如果不對(duì)此加以特別注意,增加線(xiàn)程可能不能帶來(lái)UI響應(yīng)的改善。
1 跨線(xiàn)程創(chuàng)建對(duì)話(huà)框
1.1 需求
有這樣的應(yīng)用場(chǎng)景:創(chuàng)建非模態(tài)對(duì)話(huà)框后需要馬上做些耗時(shí)的工作,而同時(shí)又希望能夠立刻在對(duì)話(huà)框上操作,所以,希望讓非模態(tài)對(duì)話(huà)框工作在單獨(dú)的線(xiàn)程上。
1.2 方案
主線(xiàn)程啟動(dòng)一個(gè)UI線(xiàn)程,并且,讓這個(gè)線(xiàn)程創(chuàng)建非模態(tài)對(duì)話(huà)框。
想法是:既然用單獨(dú)的線(xiàn)程創(chuàng)建了對(duì)話(huà)框,所以,主線(xiàn)程在創(chuàng)建UI線(xiàn)程后,就可以繼續(xù)自己其他耗時(shí)的工作了。
1.3 實(shí)現(xiàn)
用一個(gè)簡(jiǎn)單的例子程序來(lái)試驗(yàn)這個(gè)方案:在VC++中創(chuàng)建一個(gè)基于SDI的MFC應(yīng)用程序,在其CView派生類(lèi)中處理“Call”菜單命令,創(chuàng)建派生自CWinThread的UI線(xiàn)程,在這個(gè)UI線(xiàn)程初始化過(guò)程中創(chuàng)建一個(gè)對(duì)話(huà)框。
1.3.1 單獨(dú)的UI線(xiàn)程創(chuàng)建對(duì)話(huà)框
實(shí)現(xiàn)一個(gè)派生自CWinThread的UI線(xiàn)程類(lèi),其中最關(guān)鍵的是CreateMyDlg和DestroyMyDlg函數(shù)。
類(lèi)如下
class UIWorker : public CWinThread
{
DECLARE_DYNCREATE(UIWorker)
protected:
UIWorker(); // protected constructor used by dynamic creation
// Attributes
public:
// Operations
public:
bool CreateMyDlg( void );
void SetOwnerWnd( HWND hWnd );
void DestroyMyDlg( void );
……
private:
CDlgUserTest* m_pDlgTest;
HWND m_hOwnerWnd;
};
CreateMyDlg和DestroyMyDlg的實(shí)現(xiàn)都非常簡(jiǎn)單
bool UIWorker::CreateMyDlg( void )
{
m_pDlgTest = new CDlgUserTest;
if ( NULL == m_pDlgTest )
{
return false;
}
CWnd* pWnd = NULL;
if ( NULL != m_hOwnerWnd )
{
pWnd = reinterpret_cast<CWnd*>( CWnd::FromHandle( m_hOwnerWnd ));
if ( NULL == pWnd )
{
return false;
}
}
BOOL bSuccess = m_pDlgTest->Create( IDD_DIALOG_TEST, pWnd );
if ( bSuccess )
{
bSuccess = m_pDlgTest->ShowWindow( SW_SHOW );
}
return bSuccess?true:false;
}
void UIWorker::DestroyMyDlg( void )
{
if ( NULL != m_pDlgTest )
{
delete m_pDlgTest;
m_pDlgTest = NULL;
}
}
1.3.2 主線(xiàn)程創(chuàng)建UI線(xiàn)程
主線(xiàn)程就更加簡(jiǎn)單了。在菜單的對(duì)應(yīng)操作中,創(chuàng)建線(xiàn)程,為了表示主線(xiàn)程繼續(xù)工作,提供一個(gè)循環(huán)。
bool CUserView::Call(void)
{
m_pUIWorker = static_cast<UIWorker*>( AfxBeginThread(
RUNTIME_CLASS( UIWorker )));
for ( int i = 0; i < 100000; ++i )
for ( int j = 0; j < 10000; ++j )
;
return true;
}
1.4 結(jié)果:奇怪的延遲
希望達(dá)到的效果是:
主程序啟動(dòng)后,顯示一個(gè)單文檔視界面,有一個(gè)Work菜單

點(diǎn)擊Call菜單后,對(duì)話(huà)框應(yīng)該馬上彈出,顯示為:

執(zhí)行中,對(duì)話(huà)框不會(huì)馬上彈出,而會(huì)等待一定的時(shí)間,直到循環(huán)結(jié)束,CUserView的Call函數(shù)返回,對(duì)話(huà)框才會(huì)彈出。等待的時(shí)間和循環(huán)的長(zhǎng)短成正比。
2 問(wèn)題分析
2.1 不單純的對(duì)話(huà)框:要求Windows作特殊處理
對(duì)話(huà)框是一種很不單純的窗口。無(wú)論是創(chuàng)建、消息分發(fā)還是銷(xiāo)毀,Windows都會(huì)對(duì)對(duì)話(huà)框做一些特殊的處理。如果用SDK進(jìn)行對(duì)話(huà)框編程,就會(huì)發(fā)現(xiàn)創(chuàng)建對(duì)話(huà)框需要專(zhuān)門(mén)的Win32 API。而且,我們查閱平臺(tái)SDK的講述時(shí),也會(huì)發(fā)現(xiàn)對(duì)話(huà)框需要Windows進(jìn)行若干額外的“照顧”。事實(shí)上,之所以會(huì)出現(xiàn)前述的“延遲”情況,就是Windows進(jìn)行額外協(xié)調(diào)的結(jié)果。
2.2 窗口協(xié)調(diào)導(dǎo)致等待
2.2.1 Windows協(xié)調(diào)對(duì)話(huà)框彈出過(guò)程
使用SPY++研究窗口消息,會(huì)發(fā)現(xiàn)非模態(tài)對(duì)話(huà)框創(chuàng)建時(shí),原來(lái)?yè)碛薪裹c(diǎn)的窗口會(huì)收到WM_KILLFOCUS消息,而且獲得焦點(diǎn)的窗口是創(chuàng)建的對(duì)話(huà)框。
2.2.2 線(xiàn)程需要分發(fā)消息,不能堵塞
根據(jù)例子來(lái)看,這個(gè)窗口焦點(diǎn)的協(xié)調(diào)過(guò)程被上升到了線(xiàn)程協(xié)調(diào)的層次。現(xiàn)象就是:如果被去激活的窗口的線(xiàn)程被阻塞,不能立刻處理WM_KILLFOCUS消息的話(huà),創(chuàng)建對(duì)話(huà)框的線(xiàn)程也會(huì)被阻塞,對(duì)話(huà)框一直不能被顯示出來(lái),直到線(xiàn)程不再阻塞,WM_KILLFOCUS被分發(fā)和處理。
2.3 解決方案
這個(gè)問(wèn)題產(chǎn)生的原因是:在主線(xiàn)程繁忙的時(shí)候有主線(xiàn)程必須要處理的消息,也就是主線(xiàn)程消息循環(huán)因?yàn)榇翱谔幚砗瘮?shù)占用時(shí)間過(guò)長(zhǎng)而被阻塞。因此,這個(gè)問(wèn)題更多是一個(gè)設(shè)計(jì)問(wèn)題而非技術(shù)難點(diǎn)。也許,我們?cè)搯?wèn)的是:
n 我真的要用一個(gè)冗長(zhǎng)的工作來(lái)阻塞主線(xiàn)程這樣長(zhǎng)的時(shí)間嗎?
n 我是否可以在單獨(dú)的一個(gè)工作者線(xiàn)程中來(lái)處理這個(gè)長(zhǎng)的工作?
考慮了其他的可能性后,如果對(duì)上述問(wèn)題的答案仍然為“是”的話(huà),可以采取以下解決方案:因?yàn)槲覀內(nèi)鄙俚氖窍⒀h(huán),所以,加上消息循環(huán),讓對(duì)話(huà)框能夠顯示出來(lái)之后,再去進(jìn)行其他工作的處理。
bool CUserView::Call(void)
{
m_pUIWorker = static_cast<UIWorker*>( AfxBeginThread(
RUNTIME_CLASS( UIWorker )));
while ( !m_pUIWorker->GetDoneFlag())
{
MSG msg;
::GetMessage( &msg, this->m_hWnd, NULL, NULL );
::TranslateMessage( &msg );
::DispatchMessage( &msg );
}
for ( int i = 0; i < 100000; ++i )
for ( int j = 0; j < 10000; ++j )
;
return true;
}
紅色的代碼就是加上消息循環(huán)。要注意的是,相應(yīng)的線(xiàn)程類(lèi)里面應(yīng)該在顯示出對(duì)話(huà)框后設(shè)置一個(gè)標(biāo)志,并且讓主線(xiàn)程可以查詢(xún)到這個(gè)標(biāo)志,從而終止這個(gè)臨時(shí)的消息循環(huán)。
3 啟示
3.1 Windows線(xiàn)程調(diào)度
Windows的線(xiàn)程調(diào)度原則對(duì)于程序員來(lái)說(shuō)非常簡(jiǎn)單。這條原則是:程序員無(wú)法決定線(xiàn)程調(diào)度過(guò)程。
只是因?yàn)?#8220;程序員無(wú)法決定線(xiàn)程調(diào)度過(guò)程”,并不意味著程序員不應(yīng)該去了解一些特別的線(xiàn)程調(diào)度過(guò)程。在某些場(chǎng)合下,正如上面在和對(duì)話(huà)框相關(guān)的某些時(shí)機(jī),也許,Windows的線(xiàn)程調(diào)度是有明確規(guī)則的。所以,大多數(shù)情況下,程序員可以認(rèn)為Windows的線(xiàn)程調(diào)度對(duì)于自己來(lái)說(shuō)是一個(gè)黑盒。但是,某些時(shí)候,這個(gè)盒子中間發(fā)生的事情也需要了解和掌握。
3.2 對(duì)話(huà)框
對(duì)話(huà)框是一個(gè)古老的話(huà)題,很多的人仔細(xì)討論了對(duì)話(huà)框的方方面面。對(duì)話(huà)框一直是Windows里面非常特殊的一種窗口。它的消息循環(huán),與其他窗口的協(xié)調(diào)要求,都和普通的窗口有不同之處。因此,為了配合這些不同之處,Windows在線(xiàn)程協(xié)調(diào)上也做了一些手腳。
3.3 多線(xiàn)程編程更多是一種設(shè)計(jì)
更重要的啟示是:多線(xiàn)程需要更多的考慮設(shè)計(jì)。
毫無(wú)疑問(wèn),多線(xiàn)程可以使多種工作并行進(jìn)行,提高工作效率,改善界面響應(yīng)。然而,多線(xiàn)程應(yīng)用中一個(gè)麻煩的問(wèn)題是:決定對(duì)哪些工作使用單獨(dú)的線(xiàn)程。這個(gè)決定過(guò)程其實(shí)就是設(shè)計(jì)過(guò)程。如果設(shè)計(jì)方案不合理,比如,如本例子中反映出來(lái)的問(wèn)題——在主線(xiàn)程被長(zhǎng)時(shí)間的工作阻塞的情況下,增加的線(xiàn)程并不會(huì)給我們帶來(lái)明顯的響應(yīng)改善。而且,如果設(shè)計(jì)方案不合理,會(huì)帶來(lái)更多的“臨時(shí)機(jī)制”的采用,如本例中必須增加一個(gè)單獨(dú)的消息循環(huán),并且需要在兩個(gè)線(xiàn)程中就對(duì)話(huà)框是否創(chuàng)建出來(lái)進(jìn)行通訊。這樣的實(shí)現(xiàn)在很大程度上削減了希望用多線(xiàn)程帶來(lái)的好處。
發(fā)表于 @ 2007年09月17日 15:48:00|評(píng)論(1 )|編輯