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è)基于SDIMFC應(yīng)用程序,在其CView派生類(lèi)中處理“Call”菜單命令,創(chuàng)建派生自CWinThreadUI線(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è)派生自CWinThreadUI線(xiàn)程類(lèi),其中最關(guān)鍵的是CreateMyDlgDestroyMyDlg函數(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;

};

 

CreateMyDlgDestroyMyDlg的實(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é)束,CUserViewCall函數(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 )|編輯

新一篇: .NET垃圾收集器的過(guò)去現(xiàn)在和未來(lái)(一) | 舊一篇: Clear Type之父談閱讀革命(三,終結(jié)篇)

guogangj 發(fā)表于2009年3月4日 8:35:20  IP:舉報(bào)
今天才看到這篇文章,我在我的項(xiàng)目中遇到這么個(gè)問(wèn)題:在load文件的時(shí)候,可能需要很多時(shí)間,因?yàn)槲业慕缑嫒绾物@示,有相當(dāng)大一部分是根據(jù)load的內(nèi)容的,甚至我需要根據(jù)load文件的內(nèi)容加載不同的dll,所以我打算在load過(guò)程中做一個(gè)主窗口的模態(tài)對(duì)話(huà)框,上面寫(xiě)上“Please Wait...”。但模態(tài)對(duì)話(huà)框會(huì)阻止主線(xiàn)程繼續(xù)往下執(zhí)行l(wèi)oad動(dòng)作,所以我先單獨(dú)創(chuàng)建了一個(gè)線(xiàn)程,由這個(gè)線(xiàn)程創(chuàng)建模態(tài)對(duì)話(huà)框,就出現(xiàn)了此文中的“奇怪的延遲”而達(dá)不到效果,當(dāng)然我的解決方法和你的不太相同,我使用了一個(gè)Timer和一個(gè)Feedback消息來(lái)實(shí)現(xiàn)的……具體就不說(shuō)了。