win32多線程-在MFC程式中使用多線程

來源:互聯網
上載者:User
1、基礎知識介紹

    使用mfc的道友,應該很清楚,在mfc程式中建立多線程時,應該調用mfc中為我們設計好的線程介面函數AfxBeginThread(),原因是這個介面為我們做好了mfc函數和資料的初始化工作,如果你的mfc多線程中不使用任何mfc函數或資料,你也可以不用此函數來建立多線程。好了,進入正題,我來介紹一下用AfxBeginThread()建立worker線程和UI線程的兩種調用方式。


    使用介面執行緒和工作者線程都是由AfxBeginThread建立的。MFC提供了兩個重載版的AfxBeginThread,一個用於工作者線程,另一個用於使用介面執行緒,分別有如下的原型和過程:

工作者線程的AfxBeginThread的原型如下:
CWinThread* AFXAPI AfxBeginThread(
AFX_THREADPROC pfnThreadProc, 
LPVOID pParam,
int nPriority, 
UINT nStackSize, 
DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs)
其中:
參數1  線程的入口函數,聲明一定要如下: UINT MyThreadFunction( LPVOID pParam );
參數2 傳遞入線程的參數,注意它的類型為:LPVOID,所以我們可以傳遞一個結構體入線程.
參數3、4、5分別指定線程的優先順序、堆棧大小、建立標識、安全屬性,含義同使用介面執行緒。

使用介面執行緒的AfxBeginThread的原型如下:
CWinThread* AFXAPI AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority, 
UINT nStackSize, 
DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs)
其中:
參數1是從CWinThread派生的RUNTIME_CLASS類;
參數2指定線程優先順序,如果為0,則與建立該線程的線程相同;
參數3指定線程的堆棧大小,如果為0,則與建立該線程的線程相同;
參數4是一個建立標識,如果是CREATE_SUSPENDED,則在懸掛狀態建立線程,線上程建立後線程掛起,否則線程在建立後開始線程的執行。
參數5表示線程的安全屬性,NT下有用。

AfxBeginThread()函數的傳回值是CWinThread* 指標,但是這個指標不能直接使用,因為這個指標會自動銷毀。如果道友直接使用了這個指標,那麼當在操作這個指標時,若已被mfc銷毀,那麼訪問違規將會到來。
至於傳回值的使用請看,我寫的一個mfc程式片段。

CString strName = _T("");CWinThread* pThread = NULL;UINT CBcgTestDlg::ThreadWorkFunc(LPVOID lPvoid){for (int n = 0; n < 10000; n++){strName = _T("http://blog.csdn.net/windows_nt");strName = strName + _T("\n");TRACE(strName);}return 0;}void CBcgTestDlg::OnOK() {for (int n = 0; n < 10000; ++n){if (pThread){WaitForSingleObject(pThread->m_hThread, INFINITE);delete pThread;}pThread = AfxBeginThread(ThreadWorkFunc, NULL, 0, CREATE_SUSPENDED, NULL);if (pThread){pThread->m_bAutoDelete = FALSE;pThread->ResumeThread();}}}//現在可以放心的使用傳回值pThread了,但是要記得在使用結束後記得調用delete pThread,釋放資源(CWinThread類中的線程控制代碼會在解構函式中自動釋放)。

2、mfc中使用多線程要點

工作者線程的使用和c++中使用多線程沒什麼區別,請參考我以前寫的四種線程同步的方式:
windows核心編程-關鍵段(臨界區)線程同步
windows核心編程-互斥器(Mutexes)
windows核心編程-訊號量(semaphore)
windows核心編程-核心對象線程同步

接下來我介紹一下UI線程的一些相關知識。
    AfxBeginThread()的UI線程版本,期望為你在pThreadClass參數中所指定的類配置一個對象,此類必須派生自CWinThread,CWinThread提供了一堆多樣化的虛函數,你可以改寫之以協助訊息的處理,線程的啟動和清理,以及異常情況的處理,這些虛函數列舉如下:
InitInstance()
ExitInstance()
OnIdle()
PreTranslateMessage()
IsIdleMessage()
ProcessWndProcException()
ProcessMessageFilter()
Run()
當然,你也並不是非要改寫它們不可。預設情況下只要改寫InitInstance(),mfc就會開啟一個訊息迴圈。

3、mfc中多線程mfc資料共用的限制

    mfc多線程程式有一個重大限制,會影響你所做的幾乎每一件事情。mfc各對象和win32 handles之間的映射關聯性記錄線上程局部儲存(Thread Local Storage, SLS)之中。因此,你沒有辦法把一個mfc對象從某線程手上交到另一線程手上,你也不能夠線上程之間傳遞mfc對象指標。我所謂的mfc對象包括(但不限於)CWnd、CDC、CPen、CBrush、CFont、CBitmap、CPalette、這個限制的存在阻止了“為這些對象產生同步機制”的必要性,那會大大影響mfc的速度效率。

    這個限制有幾個分歧,如果兩個線程都調用CWnd::GetDlgItem()以取得對話方塊中的一個控制項(例如 edit),那麼每個線程應該獲得不同的指標——甚至即使兩個線程的對象是一個控制項。如果面對一個指標,其所指對象並沒有永久的mfc結構,那麼當對此指標的一個索求行為出現時,mfc往往會產生一些臨時性對象。例如,CWnd::GetDlgItem()往往會在被索求一個指標(例如指向一個CEdit*或一個CStatic*)時,產生一個臨時性對象。這些對象會在下一次程式進入閑置迴圈時被清理掉。如果這些對象被許多線程共用,MFC就沒有能力預期它們的生命,也因此沒有能力執行清理工作。因此,mfc為每一個有需求的線程產生一個新對象。
    這個限制(關於線上程之間交換對象)的意思是說,你不能夠放一個指標(指向一個CWnd)到結構之中,而該結構被一個Worker用,你也不能夠把一個指向CDialog或CView的指標交給另一個線程。當你需要調用View或document中的一個成員函數,特別是像UpdateAllViews()這樣的函數時,上述的限制很快就會惡化。
    MFC在許多地方檢查"橫跨線程之對象的使用方式"。任何時候,只要MFC對著對象調用ASSERT_VALID,它便會檢查對象是否保持線上程局部儲存(TLS)中,如果你嘗試調用一個像CDocument::UpdateAllViews()這樣的函數,那麼當程式運行時,CWnd::AssertValid()會產生一個assertion.

下邊的注釋出現在MFC原始碼的WINCORE.CPP中的Cwnd::ASSERT_VALID()成員函數內。
// Note: if either of the above asserts fire and you are
// writing a multithreaded application, it is likely that
// you have passed a C++ object from one thread to another
// and have used that object in a way that was not intended.
// (only simple inline wrapper functions should be used)
//
// In general, CWnd objects should be passed by HWND from
// one thread to another.  The receiving thread can wrap
// the HWND with a CWnd object by using CWnd::FromHandle.
//
// It is dangerous to pass C++ objects from one thread to
// another, unless the objects are designed to be used in
// such a manner.
    線程局部儲存(TLS)的使用說明了以AfxBeginThread()在MFC的程式中產生UI線程的重要性。如果你用的是_beginthreadex()或CreatThread()時,mfc不會給你機會產生出用以維護其handles的必要結構。
    線上程之間共用對象,這裡倒是有一個不太方便的替代方案:不要放置MFC對象,改放對象的handle。當你把handle傳遞給新線程時,線程可以把該handle附著到一個新的mfc對象:使用FromHandle()可以產生一個臨時對象,使用Attach()則可以產生一個永久對象。例如,你把一個HDC交給線程,你可以利用以下程式碼把這個HDC附著到一個永久的CDC對象上:

//1、長時間使用HDC hOriginalDC; //收到的HDCCDC dc;dc.Attach(hOriginalDC);//在此進行繪圖操作dc.Detach();//線程退出前應該調用Detach();//2、如果線程只是想短暫地使用這個數值,它可以產生一個臨時對象,像這樣://CDC *pDC = CDC::FromHandle(hOriginalDC);

    並不是所有的mfc對象都很容易以該技術傳遞。CView就是個例子,你可以輕易取得一個view的視窗handle,並將它交給一個新線程,但最好這個新線程可以把此handle附著到一個CWnd,因為並沒有CView::FromHandle()函數可以產生一個臨時性的view,像鏡子一樣反映出原來的那一個。原來的CView結構不再可用。所以所有相關的view資訊也都不再可用了。回到UpdateAllViews(),此函數運作時所使用的指標被埋藏在document中,無法改變。因此,其他線程沒有任何方法可以調用此函數。唯一一個替代方案就是送出一個使用者自訂訊息,回到原線程中,告訴它更新它的views。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.