MFC多執行緒的建立,包括工作執行緒和使用者介面執行緒

十日十乞001發表於2017-06-01

MFC多執行緒的建立

1.MFC多執行緒簡介

MFC對多執行緒進行了一層簡單的封裝,Visual C++中每個執行緒都是從CWinThread類繼承而來的。每一個應用程式的執行都有一個主執行緒,這個主執行緒也是從CWinThread類繼承而來的。可以利用CWinThread物件建立應用程式執行的其它執行緒。

MFC用CWinThread物件來表示所有執行緒。利用MFC可以建立兩種執行緒,分別稱之為工作者執行緒和使用者介面執行緒。二者的主要區別在於工作者執行緒沒有訊息迴圈,而使用者介面執行緒有自己的訊息佇列和訊息迴圈。工作者執行緒沒有訊息機制,通常用來執行後臺計算和維護任務,如冗長的計算過程,印表機的後臺列印等。使用者介面執行緒一般用於處理獨立於其他執行緒執行之外的使用者輸入,響應使用者及系統所產生的事件和訊息等。但對於Win32API程式設計而言,這兩種執行緒是沒有區別的,它們都只需執行緒的啟動地址即可啟動執行緒來執行任務。

2.MFC多執行緒基礎

為了深入瞭解MFC下建立執行緒的方法,我們先深入學習一下CWinThread類。CWinThread類在MFC類結構中的位置如下圖所示:

圖1:CWinThread類在mfc類結構中的位置

詳細的MFC類結構圖請參考MSDN: http://msdn.microsoft.com/zh-cn/library/ws8s10w4(VS.90).aspx

首先看一下類CWinThread的宣告。類CWinThread的宣告在afxwin.h中:

/////////////////////////////////////////////////////////////////////////////////////////////////

Class CWinThread :publicCCmdTarget

{

    DECLARE_DYNAMIC(CWinThread)

public:

//建構函式

    CWinThread();

//用來具體建立執行緒的函式

    BOOL CreateThread(DWORDdwCreateFlags = 0,UINTnStackSize = 0,

       LPSECURITY_ATTRIBUTES lpSecurityAttrsNULL);

// Attributes

//主視窗,(通常使用AfxGetApp()->m_pMainWnd可以得到)

    CWndm_pMainWnd;       // main window(usually same AfxGetApp()->m_pMainWnd)

//活動視窗,可能不是主視窗

    CWndm_pActiveWnd;     // active mainwindow (may not be m_pMainWnd)

    BOOL m_bAutoDelete;     // enables'delete this' after thread termination

    // only valid while running

//該執行緒的控制程式碼

    HANDLE m_hThread;       // thisthread's HANDLE

    operator HANDLE() const;

//該執行緒的ID

    DWORD m_nThreadID;      // thisthread's ID

//執行緒優先順序

    int GetThreadPriority();

    BOOL SetThreadPriority(int nPriority);

// Operations

//掛起執行緒

    DWORD SuspendThread();

//啟動執行緒

    DWORD ResumeThread();

//傳送執行緒訊息

    BOOL PostThreadMessage(UINT messageWPARAM wParamLPARAM lParam);

// Overridables

    // 執行緒初始化,每個應用程式都可以過載該函式

    virtual BOOL InitInstance();

    // running and idle processing

    virtual int Run();

    virtual BOOL PreTranslateMessage(MSG*pMsg);

    virtual BOOL PumpMessage();    // low level message pump

    virtual BOOL OnIdle(LONGlCount);// return TRUEif more idle processing

    virtual BOOL IsIdleMessage(MSG*pMsg); // checks for special messages

    // thread termination

    virtual int ExitInstance(); //default will 'delete this'

    // Advanced: exception handling

    virtual LRESULT ProcessWndProcException(CException*e,constMSG*pMsg);

    // Advanced: handling messages sent to message filter hook

    virtual BOOL ProcessMessageFilter(intcode,LPMSGlpMsg);

    // Advanced: virtual access to m_pMainWnd

    virtual CWndGetMainWnd();

// Implementation

public:

    virtual ~CWinThread();

#ifdef _DEBUG

    virtual void AssertValid() const;

    virtual void Dump(CDumpContext&dc)const;

#endif

    void CommonConstruct();

    virtual void Delete();

       // 'delete this' only if m_bAutoDelete == TRUE

public:

    // constructor used by implementation of AfxBeginThread

    CWinThread(AFX_THREADPROCpfnThreadProc,LPVOIDpParam);

    // valid after construction

    LPVOID m_pThreadParams;// generic parameters passed to starting function

    AFX_THREADPROC m_pfnThreadProc;

    // set after OLE is initialized

    void (AFXAPI*m_lpfnOleTermOrFreeLib)(BOOL,BOOL);

    COleMessageFilterm_pMessageFilter;

protected:

    BOOL DispatchThreadMessageEx(MSGmsg);  // helper

    void DispatchThreadMessage(MSGmsg);  // obsolete

};

/////////////////////////////////////////////////////////////////////

有些函式是不是很眼熟呀,在前面的文章中已經介紹和使用過啦。MFC類就是這樣的,它無非就是簡單封裝一些API函式,並新增一些自己的函式而構成的。不用MFC我們照樣可以編寫很優秀的程式,MFC的宗旨就是簡化程式設計,讓你可以很容易入門和簡單的使用,也催生了大量的程式設計師。但對喜歡刨根問底的朋友卻是一道很厚的牆

下面幾個函式是多執行緒程式設計中經常用到的幾個全域性函式:

//建立工作執行緒

CWinThreadAFXAPIAfxBeginThread(

AFX_THREADPROC pfnThreadProc,//執行緒函式

 LPVOID pParam,//傳給執行緒函式的引數

int nPriority =THREAD_PRIORITY_NORMAL,//執行緒的優先順序

UINT nStackSize = 0,//堆疊大小

DWORD dwCreateFlags = 0,//建立起始狀態標誌

   LPSECURITY_ATTRIBUTES lpSecurityAttrsNULL//執行緒的安全屬性

);

//建立使用者介面執行緒

CWinThreadAFXAPIAfxBeginThread(

CRuntimeClasspThreadClass,//從CWinThread派生的類的RUNTIME_CLASS

int nPriority =THREAD_PRIORITY_NORMAL,//執行緒的優先順序

   UINT nStackSize = 0,// 堆疊大小

DWORD dwCreateFlags = 0,// 建立起始狀態標誌

 LPSECURITY_ATTRIBUTESlpSecurityAttrs =NULL//執行緒的安全屬性

);

//獲取執行緒物件

CWinThreadAFXAPIAfxGetThread();

//獲取當前訊息

MSGAFXAPIAfxGetCurrentMessage();

//結束執行緒執行

void AFXAPIAfxEndThread(UINTnExitCode,BOOLbDelete =TRUE);

//初始化執行緒

void AFXAPIAfxInitThread();

//終止執行緒執行

void AFXAPIAfxTermThread(HINSTANCEhInstTerm =NULL);

仔細閱讀以上類的說明能學到不少東西:

(1)   CWinThead類通過CreateThread()成員函式來建立執行緒,這個函式的宣告和Win32APICreateThread()的引數相似

(2)每個函式在執行後都有一個控制程式碼和ID號。

(3)通過設定屬性m_bAutoDelete,可決定執行緒在執行結束後執行緒物件是否自動刪除,它的訪問許可權是public型的,可以直接進行設定。一般情況下,執行緒物件的生命週期和執行緒的生命週期一致。如果你想改變執行緒物件的生命週期,可設定該屬性為FALSE。

(4)MFC下的多執行緒仍然支援執行緒的掛起和啟動。

(5)具有PreTranslateMessage()、PumpMessage()等函式,供使用者介面執行緒的訊息機制使用。

在MFC中實際上是呼叫AfxBeginThread()函式來建立執行緒的。那麼為什麼不直接使用::CreateThread()_beginthread()函式來建立執行緒呢?只要看一下CWinThread類的實現中的相關程式碼就明白了。在thrdcore.cpp檔案中的相關程式碼如下:

==========================================================================================================

CWinThreadAFXAPI AfxGetThread()

{

    // check for current thread in module thread state

    AFX_MODULE_THREAD_STATEpStateAfxGetModuleThreadState();

    CWinThreadpThreadpState->m_pCurrentWinThread;

    return pThread;

}


MSGAFXAPI AfxGetCurrentMessage()

{

  _AFX_THREAD_STATEpStateAfxGetThreadState();

  ASSERT(pState);

  return &(pState->m_msgCur);

}


CWinThread* AFXAPI AfxBeginThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam,int nPriority,UINT nStackSize, DWORD dwCreateFlags,

    LPSECURITY_ATTRIBUTESlpSecurityAttrs)

{

#ifndef _MT

    pfnThreadProc;

    pParam;

    nPriority;

    nStackSize;

    dwCreateFlags;

    lpSecurityAttrs;

    return NULL;

#else

    ASSERT(pfnThreadProc!=NULL);

    CWinThread* pThread=DEBUG_NEWCWinThread(pfnThreadProc,pParam);

    ASSERT_VALID(pThread);

    if (!pThread->CreateThread(dwCreateFlags|CREATE_SUSPENDED,nStackSize,

       lpSecurityAttrs))

    {

       pThread->Delete();

       return NULL;

    }

    VERIFY(pThread->SetThreadPriority(nPriority));

    if (!(dwCreateFlags&CREATE_SUSPENDED))

       VERIFY(pThread->ResumeThread() != (DWORD)-1);

    return pThread;

#endif //!_MT)

}


CWinThread* AFXAPI AfxBeginThread(CRuntimeClass*pThreadClass,

    int nPriorityUINT nStackSize,DWORD dwCreateFlags,

    LPSECURITY_ATTRIBUTES lpSecurityAttrs)

{

#ifndef _MT

    pThreadClass;

    nPriority;

    nStackSize;

    dwCreateFlags;

    lpSecurityAttrs;

    return NULL;

#else

    ASSERT(pThreadClass!=NULL);

    ASSERT(pThreadClass->IsDerivedFrom(RUNTIME_CLASS(CWinThread)));

    CWinThread* pThread= (CWinThread*)pThreadClass->CreateObject();

    if (pThread ==NULL)

       AfxThrowMemoryException();

    ASSERT_VALID(pThread);

    pThread->m_pThreadParams=NULL;

    if (!pThread->CreateThread(dwCreateFlags|CREATE_SUSPENDED,nStackSize,

       lpSecurityAttrs))

    {

       pThread->Delete();

       return NULL;

    }

    VERIFY(pThread->SetThreadPriority(nPriority));

    if (!(dwCreateFlags&CREATE_SUSPENDED))

    {

       ENSURE(pThread->ResumeThread() != (DWORD)-1);

    }

    return pThread;

#endif //!_MT

}

 

void AFXAPI AfxEndThread(UINTnExitCode,BOOLbDelete)

{

#ifndef _MT

    nExitCode;

    bDelete;

#else

    // remove current CWinThread object from memory

    AFX_MODULE_THREAD_STATEpStateAfxGetModuleThreadState();

    CWinThreadpThreadpState->m_pCurrentWinThread;

    if (pThread !=NULL)

    {

       ASSERT_VALID(pThread);

       ASSERT(pThread!=AfxGetApp());

       // cleanup OLE if required

       if (pThread->m_lpfnOleTermOrFreeLib !=NULL)

           (*pThread->m_lpfnOleTermOrFreeLib)(TRUE,FALSE);

       if (bDelete)

           pThread->Delete();

       pState->m_pCurrentWinThread=NULL;

    }

    // allow cleanup of any thread local objects

    AfxTermThread();

    // allow C-runtime to cleanup, and exit the thread

    _endthreadex(nExitCode);

#endif //!_MT

}

/////////////////////////////////////////////////////////////////////

// Global functions forthread initialization and thread cleanup

 

LRESULT CALLBACK _AfxMsgFilterHook(intcode,WPARAMwParam,LPARAMlParam);

 

void AFXAPI AfxInitThread()

{

    if (!afxContextIsDLL)

    {

       // set message filter proc

       _AFX_THREAD_STATEpThreadStateAfxGetThreadState();

       ASSERT(pThreadState->m_hHookOldMsgFilter ==NULL);

       pThreadState->m_hHookOldMsgFilter= ::SetWindowsHookEx(WH_MSGFILTER,

           _AfxMsgFilterHookNULL,::GetCurrentThreadId());

    }

}

 

extern CThreadSlotData_afxThreadData;

void AFXAPI AfxTermThread(HINSTANCEhInstTerm)

{

    try

    {

#ifdef _DEBUG

       // check for missing AfxLockTempMap calls

       if (AfxGetModuleThreadState()->m_nTempMapLock != 0)

       {

           TRACE(traceAppMsg,0,"Warning: Temp map lock count non-zero(%ld).\n",

              AfxGetModuleThreadState()->m_nTempMapLock);

       }

#endif

       AfxLockTempMaps();

       AfxUnlockTempMaps(-1);

    }

    catchCException*e )

    {

       e->Delete();

    }

    try

    {

       // cleanup thread local tooltip window

       if (hInstTerm ==NULL)

       {

           AFX_MODULE_THREAD_STATE*pModuleThreadState=AfxGetModuleThreadState();

           if ((pModuleThreadState!=NULL) &&

              (pModuleThreadState->m_pToolTip!=NULL))

           {

              pModuleThreadState->m_pToolTip->DestroyWindow();

              delete pModuleThreadState->m_pToolTip;

              pModuleThreadState->m_pToolTip=NULL;

           }

       }

    }

    catchCException*e )

    {

       e->Delete();

    }

    try

    {

       // cleanup the rest of the thread local data

       if (_afxThreadData!=NULL)

           _afxThreadData->DeleteValues(hInstTerm,FALSE);

    }

    catchCException*e )

    {

       e->Delete();

    }

}

/////////////////////////////////////////////////////////////////////

// CWinThread construction

 

CWinThread::CWinThread(AFX_THREADPROCpfnThreadProc,LPVOIDpParam)

{

    m_pfnThreadProc = pfnThreadProc;

    m_pThreadParams = pParam;

    CommonConstruct();

}

 

CWinThread::CWinThread()

{

    m_pThreadParams = NULL;

    m_pfnThreadProc = NULL;

    CommonConstruct();

}

 

void CWinThread::CommonConstruct()

{

    m_pMainWnd = NULL;

    m_pActiveWnd = NULL;

    // no HTHREAD until it is created

    m_hThread = NULL;

    m_nThreadID = 0;

    _AFX_THREAD_STATEpStateAfxGetThreadState();

    // initialize message pump

#ifdef _DEBUG

    pState->m_nDisablePumpCount= 0;

#endif

    pState->m_msgCur.message =WM_NULL;

    pState->m_nMsgLast=WM_NULL;

    ::GetCursorPos(&(pState->m_ptCursorLast));

    // most threads are deleted when not needed

    m_bAutoDelete = TRUE;

    // initialize OLE state

    m_pMessageFilter = NULL;

    m_lpfnOleTermOrFreeLib = NULL;

}

 

CWinThread::~CWinThread()

{

    // free thread object

    if (m_hThread !=NULL)

       CloseHandle(m_hThread);

    // cleanup module state

    AFX_MODULE_THREAD_STATEpStateAfxGetModuleThreadState();

    if (pState->m_pCurrentWinThread ==this)

       pState->m_pCurrentWinThread=NULL;

}

 

BOOL CWinThread::CreateThread(DWORDdwCreateFlags,UINTnStackSize,

    LPSECURITY_ATTRIBUTES lpSecurityAttrs)

{

#ifndef _MT

    dwCreateFlags;

    nStackSize;

    lpSecurityAttrs;

    return FALSE;

#else

    ENSURE(m_hThread==NULL); // already created?

    // setup startup structure for thread initialization

    _AFX_THREAD_STARTUP startup;memset(&startup,0,sizeof(startup));

    startup.pThreadState=AfxGetThreadState();

    startup.pThread=this;

    startup.hEvent= ::CreateEvent(NULL,TRUE,FALSE,NULL);

    startup.hEvent2= ::CreateEvent(NULL,TRUE,FALSE,NULL);

    startup.dwCreateFlags=dwCreateFlags;

    if (startup.hEvent ==NULL||startup.hEvent2==NULL)

    {

       TRACE(traceAppMsg,0,"Warning: CreateEvent failed inCWinThread::CreateThread.\n");

       if (startup.hEvent !=NULL)

           ::CloseHandle(startup.hEvent);

       if (startup.hEvent2 !=NULL)

           ::CloseHandle(startup.hEvent2);

       return FALSE;

    }

 

    // create the thread (it may or may not start to run)

    m_hThread = (HANDLE)(ULONG_PTR)_beginthreadex(lpSecurityAttrs,nStackSize

       &_AfxThreadEntry, &startup,dwCreateFlags |CREATE_SUSPENDED,(UINT*)&m_nThreadID);

    if (m_hThread ==NULL)

    {

       ::CloseHandle(startup.hEvent);

       ::CloseHandle(startup.hEvent2);

       return FALSE;

    }

 

    // start the thread just for MFC initialization

    VERIFY(ResumeThread()!= (DWORD)-1);

    VERIFY(::WaitForSingleObject(startup.hEvent,INFINITE) ==WAIT_OBJECT_0);

    ::CloseHandle(startup.hEvent);

    // if created suspended, suspend it until resume threadwakes it up

    if (dwCreateFlags&CREATE_SUSPENDED)

       VERIFY(::SuspendThread(m_hThread) != (DWORD)-1);

    // if error during startup, shut things down

    if (startup.bError)

    {

       VERIFY(::WaitForSingleObject(m_hThread,INFINITE)==WAIT_OBJECT_0);

       ::CloseHandle(m_hThread);

       m_hThread = NULL;

       ::CloseHandle(startup.hEvent2);

       return FALSE;

    }

 

    // allow thread to continue, once resumed (it may alreadybe resumed)

    VERIFY(::SetEvent(startup.hEvent2));

    return TRUE;

#endif //!_MT

}

 

void CWinThread::Delete()

{

    // delete thread if it is auto-deleting

    if (m_bAutoDelete)

       delete this;

}

/////////////////////////////////////////////////////////////////////

在建立一個新的執行緒時,不必直接建立執行緒物件,因為執行緒物件是由全域性函式AxCreateThread()自動產生的。只要首先定義一個CWinThread類指標,然後呼叫全域性函式AxCreateThread()來產生一個新的執行緒物件,並呼叫執行緒類的CreateThread()成員函式來具體建立執行緒,最後將新的執行緒物件的指標返回。應該將其存在CWinThread變數中,以便能夠進一步控制執行緒。

   AxCreateThread()函式和CWinThread:: CreateThread()函式並不是簡單地對_beginthreadex()函式進行了一下封裝,它還做了一些應用程式框架所需的內部資料的初始化工作,並保證使用正確的C執行庫版本,因為在兩個函式一開始就要檢查環境引數_MT是否已經定義。而且在建立執行緒過程中還進行了很多檢查和測試,保證能夠正確產生新的執行緒,同時線上程建立失敗時正確地釋放掉已經分配的資源。

注意事項:

(1)一般情況下,推薦使用AfxBeginThread()來一次性地建立並啟動一個執行緒,但是也可以通過兩步法來建立執行緒:首先建立CWinThread類的一個物件,然後呼叫該物件的成員函式CreateThread()來啟動該執行緒。

(2)MFC支援兩類多執行緒,即工作執行緒和使用者介面執行緒。使用者介面執行緒經常過載InitInstance()和ExitInstance()函式,用以控制使用者介面執行緒例項的初始化和必要的清理工作。工作者執行緒一般不使用

3.執行緒函式

(1)工作執行緒的執行緒函式:從AfxBeginThread()函式的引數可以看出:

AFX_THREADPROC pfnThreadProc,//執行緒函式

 LPVOID pParam,               //傳給執行緒函式的引數

其中AFX_THREADPROC為一個巨集,其定義如下:

typedef UINT(AFX_CDECL *AFX_THREADPROC)(LPVOID);

從以上語句,可以得到工作執行緒的執行緒函式的形式為:

UINT ThreadFunc(LPVOID pParm);

ThreadFunc()函式應返回一個UINT型別的值,用以指明該函式結束的原因。一般情況下,返回0表明執行成功。

pParam:傳遞給執行緒函式的一個32位引數,執行函式將用某種方式解釋該值。它可以是數值,或是指向一個結構的指標,甚至可以被忽略。

(2)使用者介面執行緒的執行緒函式:從AfxBeginThread()函式的引數可以看出:

CRuntimeClasspThreadClass,//從CWinThread派生的類的RUNTIME_CLASS

即pThreadClass 是指向 CWinThread 的一個匯出類的執行時類物件的指標,該匯出類定義了被建立的使用者介面執行緒的啟動、退出等。

有了上面的基礎,下面來學習執行緒的建立。

4.工作執行緒的建立

工作執行緒沒有訊息機制經常用來完成一些後臺工作,如計算、列印、等待、迴圈等,這樣使用者就不必因為計算機在從事繁雜而耗時的工作而等待。建立一個工作執行緒相對比較簡單。

我們用一個例項來演示工作執行緒的建立。查詢N(很大)以內的素數,為了演示效果,N的取值很大(比如:10000等),並在主介面實時動態顯示處理結果,並顯示處理進度等。

先說下素數的定義:素數又稱質數,指在一個大於1的自然數中,除了1和此整數自身外,不能被其他自然數整除的數。

一個數 如果是素數,那麼它的所有的因子不超過sqrt(n)(n的平方)那麼我們可以用這個性質用判斷一個數是否是素數。在此不討論該演算法的複雜度,只是演示效果,望大牛們不要拍磚哈。

主要程式:

//.h檔案

UINT ThreadFunc(LPVOID pParm);//執行緒函式的定義

bool IsPrime(UINT Number );   //素數判斷


struct threadInfo

{

    unsigned int nRange;//範圍

    HWND        hWnd;//主視窗控制程式碼,用於訊息的傳送

};

private:

CWinThread *m_pThread;

//.cpp檔案

//開始

void CAfxBeginThread1Dlg::OnBnClickedButtonCal()

{

    // TODO: 在此新增控制元件通知處理程式程式碼

    m_nNum = 1;

    m_List.ResetContent();

    UpdateData(TRUE);

    m_stTip.SetWindowText("");

    if(m_nRange<2)

    {

       AfxMessageBox("查詢範圍必須是大於的整數!",MB_OK|MB_ICONERROR);

       return;

    }

    GetDlgItem(IDC_BUTTON_CAL)->EnableWindow(FALSE);

    m_Progress.SetPos(0);

    m_Progress.SetRange32(0,m_nRange);

    m_Info.nRange =m_nRange;

    m_Info.hWnd =m_hWnd;

    m_pThread = AfxBeginThread(ThreadFunc,&m_Info);

    if (m_pThread ==NULL)

    {

       AfxMessageBox("啟動失敗!",MB_OK|MB_ICONERROR);

       return;

    }

}

 

UINT ThreadFunc(LPVOID pParm)

{

    threadInfo *pInfo=(threadInfo*)pParm;

    bool bResult = false;

    for (inti =2;i<=pInfo->nRange;i++)

    {

       bResult = IsPrime(i);

       ::SendMessage(pInfo->hWnd,WM_INFO,bResult,i);

       //Sleep(1000);

    }

    //結束

    ::SendMessage(pInfo->hWnd,WM_INFO,0,-1);

    return 0;

}

 

//素數判斷

bool IsPrime(UINT nNumber )

{//定理:如果n不是素數, 則n有滿足<d<=sqrt(n)的一個因子d.

    if(nNumber < 2)returnfalse;

    if(nNumber == 2)returntrue;

    for (inti = 3;i*i<=nNumber;i += 2)

    {

        if(nNumber%i == 0)

            return false;

    }

    return true;

}

 

//顯示訊息處理函式

LRESULT CAfxBeginThread1Dlg::OnMyInfo(WPARAMwParam,LPARAMlParam)

{

    if (wParam == 0&&lParam == -1)

    {//結束

       m_stTip.SetWindowText("完成");

       GetDlgItem(IDC_BUTTON_CAL)->EnableWindow(TRUE);

    }

    else

    {//是素數

       m_Progress.SetPos(lParam);

       CString str;

       str.Format("%d",lParam);

       m_stTip.SetWindowText(str);

       if (wParam)

       {

           str.Format("%d個",m_nNum);

           GetDlgItem(IDC_STATIC1)->SetWindowText(str);

           str.Format("第%d個:%d",m_nNum++,lParam);

           m_List.AddString(str);

       }  

    }  

    return 0;

}

結果預覽:

工程原始碼下載地址:

http://download.csdn.net/detail/cbnotes/4923353

歡迎大家修改和指正。

 

注意事項:

(1)該方式的多執行緒和前面的_beginthreadex方式很想象,一般用於後臺的工作執行。

(2)注意資訊的實時顯示。

(3)建立執行緒時多引數的傳遞,此處採用結構體的方式。

5.使用者介面執行緒的建立

由於使用者介面執行緒含有自己的訊息迴圈,可以處理Windows訊息,並可建立和管理諸如視窗和控制元件等使用者介面元素。因此,這種執行緒較工作執行緒更為複雜。

建立使用者介面執行緒的起點是MFCCWinThread類派生一個定製的執行緒類,而不是呼叫AfxBeginThead()函式。定製的執行緒類必須過載InitInstance()函式,該函式用來執行初始化任務,在建立執行緒時系統將呼叫InitInstance()函式。最好還要過載ExitInstane()函式,該函式是InitInstance()函式的對應,MFC在刪除執行緒物件之前會呼叫ExitInstane()函式,以便執行緒能夠在結束後清除自身。

使用者介面執行緒的建立有兩種方法,方法一是首先從CWinThread類派生一個類(必須要用巨集DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE對該類進行宣告和實現),然後呼叫AfxBeginThead()建立CWinThread派生類的物件進行初始化,啟動執行緒執行。方法二是先通過建構函式建立類CWinThread的一個物件,然後由程式設計師呼叫函式::CreateThread來啟動執行緒。通常CWinThread類的物件在該執行緒的生存期結束時將自動終止,如果程式設計師希望自己來控制,則需要將m_bAutoDelete設為FALSE。這樣線上程終止之後,CWinThread類物件仍然存在,此時需要手動刪除CWinThread物件。

5.1使用者介面執行緒例項1

     該例項主要演示建立使用者介面執行緒的第一種方法,先從CWinThread類派生一個類,然後呼叫AfxBeginThead()建立CWinThread派生類的物件進行初始化,啟動執行緒執行。該例項主要演示檔案的移動,並實施顯示操作的進度。設計到兩個執行緒:檔案的讀寫採用工作執行緒,進度顯示採用使用者介面執行緒。

主要程式:

//.h檔案

define  STEPLEN 1024*64     //檔案一次讀寫的進步k

UINT ThreadFunc(LPVOID pParm);//執行緒函式的定義:檔案複製執行緒

CWinThread *pUIThread;//介面執行緒,用於進度的顯示

CWinThread *pThread//工作執行緒,用於檔案的複製

CString m_szSrcPath;  //原始檔路徑

CString m_szDesPath//目標檔案路徑

 

//.CPP檔案

//檔案複製模組:

void CAfxBeginThead2Dlg::OnBnClickedButtonCopy()

{

    // TODO: 在此新增控制元件通知處理程式程式碼

    if (m_szDesPath.IsEmpty() ||m_szSrcPath.IsEmpty())

    {

       AfxMessageBox("原始檔路徑和目標檔案路徑不能為空!",MB_OK|MB_ICONERROR);

       return;

    }

   //

    GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(FALSE);

    //建立使用者介面執行緒,用於進度的顯示

    pUIThread=AfxBeginThread(RUNTIME_CLASS(CcbCopyFile));

    if (pUIThread ==NULL)

    {

       AfxMessageBox("使用者介面執行緒啟動失敗!",MB_OK|MB_ICONERROR);

       GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(TRUE);

       return;

    }

    //傳遞引數

   pUIThread->PostThreadMessage(WM_THREADINFO,0,(LPARAM)m_szSrcPath.GetBuffer(0));

pUIThread->PostThreadMessage(WM_THREADINFO,1,(LPARAM)m_szDesPath.GetBuffer(0));

    //建立工作執行緒,用於檔案的複製

    pThread=AfxBeginThread(ThreadFunc,this);

    if (pThread ==NULL)

    {

       AfxMessageBox("工作執行緒啟動失敗!",MB_OK|MB_ICONERROR);

       GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(TRUE);

       return;

    }

    SetTimer(1,1000,NULL);//速度統計

    SetTimer(2,100,NULL);//操作計時

    m_stTip.SetWindowText("進行中...");

    pUIThread->PostThreadMessage(WM_THREADINFO,2,1);//啟動

}

 

//檔案複製執行緒

UINT ThreadFunc(LPVOID pParm)

{

    CAfxBeginThead2Dlg*pInfo=(CAfxBeginThead2Dlg*)pParm;

   //開啟原始檔,得到檔案的大小,並一塊一塊的讀取

    CFile ReadFile;

    BOOL bOpen = ReadFile.Open(pInfo->m_szSrcPath,CFile::modeRead);

    if(!bOpen)

    {

       AfxMessageBox(pInfo->m_szSrcPath +_T("  :檔案開啟失敗!"),MB_ICONERROR|MB_OK);

       pInfo->m_stTip.SetWindowText("複製【失敗】!");

       pInfo->GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(TRUE);

       return 1;

    }

    //得到檔案的大小,用於計算進度

    DWORD dwTotalSizeReadFile.GetLength();

    DWORD dwCompleteSize= 0;//已完成的大小

    //計算檔案去讀的步長

    DWORD dwStep = STEPLEN > dwTotalSizedwTotalSize : STEPLEN;

    //資料緩衝區

    char *pBuf =newchar[dwStep+1];

    memset(pBuf,0x00,dwStep+1);

    DWORD dwRead = dwStep;

   //建立目標檔案,若目標檔案存在則清空

    CFile WriteFile;

    bOpen = WriteFile.OpenpInfo->m_szDesPath,CFile::modeCreate |CFile::modeWrite);

    if(!bOpen)

    {

       AfxMessageBox(pInfo->m_szDesPath +_T("  :檔案開啟失敗!"),MB_ICONERROR|MB_OK);

       pInfo->m_stTip.SetWindowText("複製【失敗】!");

       pInfo->GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(TRUE);

       return 2;

    }

    //檔案的複製:從原始檔去讀取資料,並寫入到目標檔案中

    while( (dwRead =ReadFile.Read(pBuf,dwStep))> 0 )

    {//讀取原始檔,一次一塊

       //將讀取的資料寫入目標檔案中

       WriteFile.Write(pBuf,dwRead);

       dwCompleteSize += dwRead;

       pInfo->m_nSpeed+=dwRead;

       //更新進度

       pInfo->pUIThread->PostThreadMessage(WM_THREADINFO,3, (LPARAM)int((dwCompleteSize*1.0/dwTotalSize)*100));

    }

    //完成

    delete pBuf;

    //關閉檔案

    ReadFile.Close();

    WriteFile.Close();

    //傳送結束訊息,用於關閉進度顯示模組

    pInfo->pUIThread->PostThreadMessage(WM_THREADINFO,10, 1);

    pInfo->KillTimer(1);

    pInfo->KillTimer(2);

    pInfo->m_stTip.SetWindowText("複製完成!");

    //使能複製按鈕,以便可以繼續進行

    pInfo->GetDlgItem(IDC_BUTTON_COPY)->EnableWindow(TRUE);

    return 0;

}

//使用者介面執行緒類:派生於CWinThread類

//檔案複製模組:標頭檔案

 

class CcbCopyFile : publicCWinThread

{

    DECLARE_DYNCREATE(CcbCopyFile)

 

protected:

    CcbCopyFile();           // 動態建立所使用的受保護的建構函式

    virtual ~CcbCopyFile();

    CString m_szSrcPath,m_szDesPath;

 

public:

    CCopyFileDlg *m_pProgressDlg;//進度介面

    virtual BOOL InitInstance();

    virtual int  ExitInstance();

    afx_msg void OnThreadInfo(WPARAMwParam,LPARAMlParam);

protected:

    DECLARE_MESSAGE_MAP()

};

===================================================================

// cbCopyFile.cpp : 實現檔案

//

#include "stdafx.h"

#include "AfxBeginThead2.h"

#include "cbCopyFile.h"


// CcbCopyFile

 

IMPLEMENT_DYNCREATE(CcbCopyFile,CWinThread)

 

CcbCopyFile::CcbCopyFile()

{

    m_pProgressDlg = NULL;

}

 

CcbCopyFile::~CcbCopyFile()

{

}

 

BOOL CcbCopyFile::InitInstance()

{

    // TODO: 在此執行任意逐執行緒初始化

    return TRUE;

}

 

int CcbCopyFile::ExitInstance()

{

    // TODO: 在此執行任意逐執行緒清理

    return CWinThread::ExitInstance();

}

 

BEGIN_MESSAGE_MAP(CcbCopyFile,CWinThread)

    ON_THREAD_MESSAGE(WM_THREADINFO,&CcbCopyFile::OnThreadInfo)

END_MESSAGE_MAP()

 

// CcbCopyFile 訊息處理程式

 

//顯示訊息處理函式

void CcbCopyFile::OnThreadInfo(WPARAMwParam,LPARAMlParam)

{

    if (wParam == 0)

    {//原始檔路徑引數

       m_szSrcPath.Format("%s",lParam);

       //AfxMessageBox(m_szSrcPath);

    }

    else if (wParam == 1)

    {//目標檔案路徑引數

       m_szDesPath.Format("%s",lParam);

       //AfxMessageBox(m_szDesPath);

    }

    else if (wParam == 2)

    {//啟動

       m_pProgressDlg = newCCopyFileDlg;

       m_pProgressDlg->Create(IDD_DIALOG1);

       m_pProgressDlg->m_szSrcPath=m_szSrcPath;

       m_pProgressDlg->m_szDesPath=m_szDesPath;

       m_pProgressDlg->UpdateData(FALSE);

       m_pProgressDlg->ShowWindow(TRUE);

    }

    else if (wParam == 3)

    {//進度

       m_pProgressDlg->m_Progress.SetPos(lParam);

    }

    else if (wParam == 4)

    {//速度

       m_pProgressDlg->UpdateSpeed(lParam);

    }

    else if (wParam == 5)

    {//時間

       float *p = (float *)lParam;

       m_pProgressDlg->UpdateTime(*p);

    }

    else

    {//完成

       m_pProgressDlg->OnCancel();

    }

    //return 0;

}

結果預覽:

工程原始碼下載地址:

http://download.csdn.net/detail/cbnotes/4956549

歡迎大家修改和指正。

 

注意事項:

(1)該例項原始碼比較多,所以上面只貼出了重要的部分,建議下載整個工程原始碼下來學習和研究(地址見上)。

(2)該例項主要演示了檔案複製,採用了工作執行緒和使用者介面執行緒相配合。提供了實時的進度顯示、實時複製速度顯示和時間顯示。功能還是比較實用,只要稍加修改做成一個複製模組(dll用於自己的程式中,還是非常實用。對不喜歡採用CopyFile/CopyFileEx函式的朋友是一個不錯的選擇。

(3)注意在工作執行緒中,響應訊息的方式,訊息對映和平時的不一樣,主要採用ON_THREAD_MESSAGE而不是ON _MESSAGE。

(4)注意工作執行緒建立中,傳遞給執行緒函式的引數為this,即 pThread=AfxBeginThread(ThreadFunc,this);//即傳遞的是物件的地址。這對於要傳遞很多引數的是一個不錯的選擇。

訊息響應函式的格式為:void OnThreadInfo(WPARAMwParam,LPARAM lParam);

注意返回型別必須為void

(5)該例項還有一個不完善的地方,就是複製過程中按“取消”的處理,該例項中還沒有實現,望有心的朋友可以實現它,大家多多思考和動手,對你一定有很好的幫助的。

(6)使用者介面執行緒和程式的主執行緒很相像,能處理各種訊息。

5.2使用者介面執行緒例項2

該例項主要演示建立使用者介面執行緒的第二種方法,先從CWinThread類派生一個類,然後呼叫CreateThread建立CWinThread派生類的物件進行初始化,啟動執行緒執行。該例項主要演示手氣測試,設計到兩個使用者介面執行緒:數字輸入執行緒和結果判斷執行緒。

主要程式:

//主執行緒

CInputThread *m_pInputThread;//數字輸入執行緒

CResultThread  *m_pResultThread;//結果判斷執行緒

//測試

void CAfxBeginThead3Dlg::OnBnClickedButtonTest()

{

    // TODO: 在此新增控制元件通知處理程式程式碼

    m_pInputThread = newCInputThread;

    m_pInputThread->CreateThread(CREATE_SUSPENDED);

 

    m_pResultThread = newCResultThread;

    m_pResultThread->CreateThread(CREATE_SUSPENDED);

    m_pResultThread->m_hWnd=m_hWnd;

    m_pInputThread->m_pResultThread=m_pResultThread;

    m_pInputThread->ResumeThread();//執行

    m_pResultThread->ResumeThread();

    m_nTimes++;

//  UpdateInfo();

}

 

LRESULT CAfxBeginThead3Dlg::OnMessage(WPARAMwParam,LPARAMlParam)

{

    if(lParam == 1)

    {//成功一次

       m_nSucess++;

    }

    UpdateInfo();

    if(wParam == 1)

    {//再測一次

       OnBnClickedButtonTest();

    }

    return 0;

}

// 更新資訊

void CAfxBeginThead3Dlg::UpdateInfo(void)

{

    CString str;

    str.Format("統計:%d/%d",m_nSucess,m_nTimes);

    GetDlgItem(IDC_STATIC1)->SetWindowText(str);

}

//數字輸入執行緒

CResultThread  *m_pResultThread;//結果判斷執行緒

CInputDlg m_InputDlg;//輸入對話方塊

BOOL CInputThread::InitInstance()

{

    // TODO: 在此執行任意逐執行緒初始化

    m_InputDlg.DoModal();

    return TRUE;

}

int CInputThread::Run()

{

    // TODO: 在此新增專用程式碼和/或呼叫基類

   m_pResultThread->PostThreadMessage(WM_THREADMESSAGE,0,m_InputDlg.m_nInput);

    return CWinThread::Run();

}

//結果判斷執行緒

#define  WM_THREADMESSAGE WM_USER+100 //輸入數字執行緒傳送的訊息,用於結果判斷

#define WM_MESSAGEWM_USER+200//向主執行緒傳送訊息,用於返回相關資訊

BEGIN_MESSAGE_MAP(CResultThread,CWinThread)

    ON_THREAD_MESSAGE(WM_THREADMESSAGE,&CResultThread::OnThreadMessage)

END_MESSAGE_MAP()

 

//顯示訊息處理函式

void CResultThread::OnThreadMessage(WPARAMwParam,LPARAMlParam)

{

    if (wParam == 0)

    {  

       bool bSucess = false;

       CString str;

       int  nLuckNum = GetLuckNum();

       if (lParam ==nLuckNum)

       {

           bSucess = true;

           str.Format("恭喜,手氣不錯哦!\n=============\n你測試的數字為【%d】\n當前的幸運數字為【%d】\n再測一次吧?",lParam,nLuckNum);

       }

       else

       {

           bSucess = false;

           str.Format("很遺憾,差一點點哦!\n=============\n你測試的數字為【%d】\n當前的幸運數字為【%d】\n再測一次吧?",lParam,nLuckNum);

       }

       if(AfxMessageBox(str,MB_YESNO|MB_DEFBUTTON1|MB_ICONINFORMATION)==IDYES)

       {

           ::SendMessage(m_hWnd,WM_MESSAGE,1,bSucess);//傳送訊息

       }

       else

       {

           ::SendMessage(m_hWnd,WM_MESSAGE,0,bSucess);//傳送訊息

       }

    }

    else

    {

       ;

    }

}

 

// 得到幸運數字

int CResultThread::GetLuckNum(void)

{

    srand((unsigned)time(NULL));

    return rand()%10;

}

結果預覽:

工程原始碼下載地址:

http://download.csdn.net/detail/cbnotes/4958041

歡迎大家修改和指正。

 

注意事項:

(1)從CWinThread類派生的類的建構函式和解析函式預設是protected,要採用第二種方法則要修改為public,否則將出錯。

(2)注意執行緒引數的傳遞方法,和前面不一樣的。即建立執行緒時先設定為CREATE_SUSPENDED(掛起),然後設定相關引數,然後再啟動執行緒,具體可以參考程式。

m_pResultThreadnew CResultThread;

      m_pResultThread->CreateThread(CREATE_SUSPENDED);//建立執行緒時先掛起

      m_pResultThread->m_hWnd =m_hWnd;//然後相關設定引數

      m_pResultThread->ResumeThread();//再執行

(3)注意CWinThread派生類中Run()函式的過載。仔細想想下面的程式的執行原理,為什麼對話方塊結束後就會執行此處。如果將對話方塊設定為非模式對話方塊,還會正常嗎?

int CInputThread::Run()

{

          // TODO: 在此新增專用程式碼和/或呼叫基類

          m_pResultThread->PostThreadMessage(WM_THREADMESSAGE,0,m_InputDlg.m_nInput);

          return CWinThread::Run();

}

相關文章