Windows多线程与对话框

Windows 多线程与对话框

Windows的对话框是获取信息输入的主要手段,增加线程则是获得更好UI响应的重要方式。由于Windows在处理对话框时进行线程调度的特殊性,如果不对此加以特别注意,增加线程可能不能带来UI响应的改善。

1 跨线程创建对话框

1.1 需求

有这样的应用场景:创建非模态对话框后需要马上做些耗时的工作,而同时又希望能够立刻在对话框上操作,所以,希望让非模态对话框工作在单独的线程上。

1.2 方案

主线程启动一个UI线程,并且,让这个线程创建非模态对话框。

想法是:既然用单独的线程创建了对话框,所以,主线程在创建UI线程后,就可以继续自己其他耗时的工作了。

1.3 实现

用一个简单的例子程序来试验这个方案:在VC++中创建一个基于SDIMFC应用程序,在其CView派生类中处理“Call”菜单命令,创建派生自CWinThreadUI线程,在这个UI线程初始化过程中创建一个对话框。

1.3.1 单独的UI线程创建对话框

实现一个派生自CWinThreadUI线程类,其中最关键的是CreateMyDlgDestroyMyDlg函数。

类如下

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的实现都非常简单

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 主线程创建UI线程

主线程就更加简单了。在菜单的对应操作中,创建线程,为了表示主线程继续工作,提供一个循环。

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 结果:奇怪的延迟

希望达到的效果是:

主程序启动后,显示一个单文档视界面,有一个Work菜单

Windows多线程与对话框_第1张图片

<shapetype id="_x0000_t75" stroked="f" filled="f" path="m@4@5l@4@11@9@11@9@5xe" o:preferrelative="t" o:spt="75" coordsize="21600,21600"><stroke joinstyle="miter"></stroke><formulas><f eqn="if lineDrawn pixelLineWidth 0"></f><f eqn="sum @0 1 0"></f><f eqn="sum 0 0 @1"></f><f eqn="prod @2 1 2"></f><f eqn="prod @3 21600 pixelWidth"></f><f eqn="prod @3 21600 pixelHeight"></f><f eqn="sum @0 0 1"></f><f eqn="prod @6 1 2"></f><f eqn="prod @7 21600 pixelWidth"></f><f eqn="sum @8 21600 0"></f><f eqn="prod @7 21600 pixelHeight"></f><f eqn="sum @10 21600 0"></f></formulas><path o:connecttype="rect" gradientshapeok="t" o:extrusionok="f"></path><lock aspectratio="t" v:ext="edit"></lock></shapetype><shape id="_x0000_i1025" style="WIDTH: 6in; HEIGHT: 298.5pt" type="#_x0000_t75"><imagedata o:title="MainUI" src="file:///C:%5CDOCUME~1%5Choward%5CLOCALS~1%5CTemp%5Cmsohtml1%5C03%5Cclip_image001.jpg"></imagedata></shape>

点击Call菜单后,对话框应该马上弹出,显示为:

Windows多线程与对话框_第2张图片

<shape id="_x0000_i1026" style="WIDTH: 6in; HEIGHT: 298.5pt" type="#_x0000_t75"><imagedata o:title="ShowDialog" src="file:///C:%5CDOCUME~1%5Choward%5CLOCALS~1%5CTemp%5Cmsohtml1%5C03%5Cclip_image003.jpg"></imagedata></shape>

执行中,对话框不会马上弹出,而会等待一定的时间,直到循环结束,CUserViewCall函数返回,对话框才会弹出。等待的时间和循环的长短成正比。

2 问题分析

2.1 不单纯的对话框:要求Windows作特殊处理

对话框是一种很不单纯的窗口。无论是创建、消息分发还是销毁,Windows都会对对话框做一些特殊的处理。如果用SDK进行对话框编程,就会发现创建对话框需要专门的Win32 API。而且,我们查阅平台SDK的讲述时,也会发现对话框需要Windows进行若干额外的“照顾”。事实上,之所以会出现前述的“延迟”情况,就是Windows进行额外协调的结果。

2.2 窗口协调导致等待

2.2.1 Windows协调对话框弹出过程

使用SPY++研究窗口消息,会发现非模态对话框创建时,原来拥有焦点的窗口会收到WM_KILLFOCUS消息,而且获得焦点的窗口是创建的对话框。

2.2.2 线程需要分发消息,不能堵塞

根据例子来看,这个窗口焦点的协调过程被上升到了线程协调的层次。现象就是:如果被去激活的窗口的线程被阻塞,不能立刻处理WM_KILLFOCUS消息的话,创建对话框的线程也会被阻塞,对话框一直不能被显示出来,直到线程不再阻塞,WM_KILLFOCUS被分发和处理。

2.3 解决方案

这个问题产生的原因是:在主线程繁忙的时候有主线程必须要处理的消息,也就是主线程消息循环因为窗口处理函数占用时间过长而被阻塞。因此,这个问题更多是一个设计问题而非技术难点。也许,我们该问的是:

n 我真的要用一个冗长的工作来阻塞主线程这样长的时间吗?

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;

}

红色的代码就是加上消息循环。要注意的是,相应的线程类里面应该在显示出对话框后设置一个标志,并且让主线程可以查询到这个标志,从而终止这个临时的消息循环。

3 启示

3.1 Windows线程调度

Windows的线程调度原则对于程序员来说非常简单。这条原则是:程序员无法决定线程调度过程。

只是因为“程序员无法决定线程调度过程”,并不意味着程序员不应该去了解一些特别的线程调度过程。在某些场合下,正如上面在和对话框相关的某些时机,也许,Windows的线程调度是有明确规则的。所以,大多数情况下,程序员可以认为Windows的线程调度对于自己来说是一个黑盒。但是,某些时候,这个盒子中间发生的事情也需要了解和掌握。

3.2 对话框

对话框是一个古老的话题,很多的人仔细讨论了对话框的方方面面。对话框一直是Windows里面非常特殊的一种窗口。它的消息循环,与其他窗口的协调要求,都和普通的窗口有不同之处。因此,为了配合这些不同之处,Windows在线程协调上也做了一些手脚。

3.3 多线程编程更多是一种设计

更重要的启示是:多线程需要更多的考虑设计。

毫无疑问,多线程可以使多种工作并行进行,提高工作效率,改善界面响应。然而,多线程应用中一个麻烦的问题是:决定对哪些工作使用单独的线程。这个决定过程其实就是设计过程。如果设计方案不合理,比如,如本例子中反映出来的问题——在主线程被长时间的工作阻塞的情况下,增加的线程并不会给我们带来明显的响应改善。而且,如果设计方案不合理,会带来更多的“临时机制”的采用,如本例中必须增加一个单独的消息循环,并且需要在两个线程中就对话框是否创建出来进行通讯。这样的实现在很大程度上削减了希望用多线程带来的好处。

你可能感兴趣的:(多线程,编程,windows,UI,F#)