三. MFC中的多線程開發

一、MFC對多線程編程的支持

  MFC中有兩類線程,分別稱之爲工作者線程和用戶界面線程。二者的主要區別在於工作者線程沒有消息循環,而用戶界面線程有自己的消息隊列和消息循環。
  工作者線程沒有消息機制,通常用來執行後臺計算和維護任務,如冗長的計算過程,打印機的後臺打印等。用戶界面線程一般用於處理獨立於其他線程執行之外的用戶輸入,響應用戶及系統所產生的事件和消息等。但對於Win32的API編程而言,這兩種線程是沒有區別的,它們都只需線程的啓動地址即可啓動線程來執行任務。
  在MFC中,一般用全局函數AfxBeginThread()來創建並初始化一個線程的運行,該函數有兩種重載形式,分別用於創建工作者線程和用戶界面線程。兩種重載函數原型和參數分別說明如下:

(1) CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,
                      LPVOID pParam,
                      nPriority=THREAD_PRIORITY_NORMAL,
                      UINT nStackSize=0,
                      DWORD dwCreateFlags=0,
                      LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

PfnThreadProc:指向工作者線程的執行函數的指針,線程函數原型必須聲明如下:

UINT ExecutingFunction(LPVOID pParam);

請注意,ExecutingFunction()應返回一個UINT類型的值,用以指明該函數結束的原因。一般情況下,返回0表明執行成功。

  • pParam:傳遞給線程函數的一個32位參數,執行函數將用某種方式解釋該值。它可以是數值,或是指向一個結構的指針,甚至可以被忽略;
  • nPriority:線程的優先級。如果爲0,則線程與其父線程具有相同的優先級;
  • nStackSize:線程爲自己分配堆棧的大小,其單位爲字節。如果nStackSize被設爲0,則線程的堆棧被設置成與父線程堆棧相同大小;
  • dwCreateFlags:如果爲0,則線程在創建後立刻開始執行。如果爲CREATE_SUSPEND,則線程在創建後立刻被掛起;
  • lpSecurityAttrs:線程的安全屬性指針,一般爲NULL;
 (2) CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,
                      int nPriority=THREAD_PRIORITY_NORMAL,
                      UINT nStackSize=0,
                      DWORD dwCreateFlags=0,
                      LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

 

  pThreadClass 是指向 CWinThread 的一個導出類的運行時類對象的指針,該導出類定義了被創建的用戶界面線程的啓動、退出等;其它參數的意義同形式1。使用函數的這個原型生成的線程也有消息機制,在以後的例子中我們將發現同主線程的機制幾乎一樣。

 


下面我們對CWinThread類的數據成員及常用函數進行簡要說明。

  • m_hThread:當前線程的句柄;
  • m_nThreadID:當前線程的ID;
  • m_pMainWnd:指向應用程序主窗口的指針
BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,
UINT nStackSize=0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);

  該函數中的dwCreateFlags、nStackSize、lpSecurityAttrs參數和API函數CreateThread中的對應參數有相同含義,該函數執行成功,返回非0值,否則返回0。
  一般情況下,調用AfxBeginThread()來一次性地創建並啓動一個線程,但是也可以通過兩步法來創建線程:首先創建CWinThread類的一個對象,然後調用該對象的成員函數CreateThread()來啓動該線程。

virtual BOOL CWinThread::InitInstance();

  重載該函數以控制用戶界面線程實例的初始化。初始化成功則返回非0值,否則返回0。用戶界面線程經常重載該函數,工作者線程一般不使用InitInstance()。

virtual int CWinThread::ExitInstance();

  在線程終結前重載該函數進行一些必要的清理工作。該函數返回線程的退出碼,0表示執行成功,非0值用來標識各種錯誤。同InitInstance()成員函數一樣,該函數也只適用於用戶界面線程。

 <補加>要結束線程的兩種方式:

1 、這是最簡單的方式,也就是讓線程函數執行完成,此時線程正常結束.它會返回一個值,一般0是成功結束,
當然你可以定義自己的認爲合適的值來代表線程成功執行.在線程內調用AfxEndThread將會直接結束線程,此時線程的一切資源都會被回收.注意在線程中使用了CString類,則不能用AfxEndThread來進行結束線程,會有內存泄漏,只有當程序結束時,會在輸出窗口有提示多少byte泄漏了。因爲Cstring的回收有其自己的機制。建議在AfxEndThread之前先進行return。
2 、如果你想讓另一個線程B來結束線程A,那麼,你就需要在這兩個線程中傳遞信息.
不管是工作者線程還是界面線程,如果你想在線程結束後得到它的結果,那麼你可以調用:
::GetExitCodeThread函數

3.如果要向線程對象設置變量,比如m_dAtuodelete,需要先把線程掛起,設置完成後再開啓(resumethread),不然有內存訪問異常(debug下差機器明顯)。

 

二、MFC多線程編程實例

  在Visual C++ 6.0編程環境中,我們既可以編寫C風格的32位Win32應用程序,也可以利用MFC類庫編寫C++風格的應用程序,二者各有其優缺點。基於Win32的應用程序執行代碼小巧,運行效率高,但要求程序員編寫的代碼較多,且需要管理系統提供給程序的所有資源;而基於MFC類庫的應用程序可以快速建立起應用程序,類庫爲程序員提供了大量的封裝類,而且Developer Studio爲程序員提供了一些工具來管理用戶源程序,其缺點是類庫代碼很龐大。由於使用類庫所帶來的快速、簡捷和功能強大等優越性,因此除非有特殊的需要,否則Visual C++推薦使用MFC類庫進行程序開發。

我們知道,MFC中的線程分爲兩種:用戶界面線程和工作者線程。我們將分別舉例說明。

用 MFC 類庫編程實現工作者線程

例程5 MultiThread5
爲了與Win32 API對照,我們使用MFC 類庫編程實現上一節例程3 MultiThread3。

1. 建立一個基於對話框的工程MultiThread5,在對話框IDD_MULTITHREAD5_DIALOG中加入一個編輯框IDC_MILLISECOND,一個按鈕IDC_START,標題爲“開始” ,一個進度條IDC_PROGRESS1;

2. 打開ClassWizard,爲編輯框IDC_MILLISECOND添加int型變量m_nMilliSecond,爲進度條IDC_PROGRESS1添加CProgressCtrl型變量m_ctrlProgress;

 

1. 在MultiThread5Dlg.h文件中:

 

struct threadInfo
{
 UINT nMilliSecond;
 CProgressCtrl* pctrlProgress;
};


UINT ThreadFunc(LPVOID lpParam);

class CMultiThread5Dlg : public CDialog
{
public:
 CProgressCtrl m_ctrlProgress;
 int  m_nMilliSecond; 
protected:
 CWinThread* pThread; 
 afx_msg void OnStart(); 
};

 

2. 在MultiThread5Dlg.cpp文件中:

 

threadInfo Info;

void CMultiThread5Dlg::OnStart()

 UpdateData(TRUE);
 Info.nMilliSecond=m_nMilliSecond;
 Info.pctrlProgress=&m_ctrlProgress;
 
 pThread=AfxBeginThread(ThreadFunc,&Info);
}

 

UINT ThreadFunc(LPVOID lpParam)
{
 threadInfo* pInfo=(threadInfo*)lpParam;
 for(int i=0;i<100;i++)
 {
  int nTemp=pInfo->nMilliSecond;

  pInfo->pctrlProgress->SetPos(i);

  Sleep(nTemp);
 }
 return 0;
}

 

 

用 MFC 類庫編程實現用戶界面線程
用戶界面線程的執行次序與應用程序主線程相同,首先調用用戶界面線程類的InitInstance()函數,如果返回TRUE,繼續調用線程的Run()函數,該函數的作用是運行一個標準的消息循環,並且當收到WM_QUIT消息後中斷,在消息循環過程中,Run()函數檢測到線程空閒時(沒有消息),也將調用OnIdle()函數,最後Run()函數返回,MFC調用ExitInstance()函數清理資源。
  你可以創建一個沒有界面而有消息循環的線程,例如:你可以從CWinThread派生一個新類,在InitInstance函數中完成某項任務並返回FALSE,這表示僅執行InitInstance函數中的任務而不執行消息循環,你可以通過這種方法,完成一個工作者線程的功能。

 

例程6 MultiThread6

建立一個基於對話框的工程MultiThread6,在對話框IDD_MULTITHREAD6_DIALOG中加入一個按鈕IDC_UI_THREAD,標題爲“用戶界面線程” 。主對話框爲CMultiThread6Dlg。

1. 右擊工程並選中“New Class…”爲工程添加基類爲CWinThread派生線程類CUIThread。

2. 給工程添加新對話框IDD_UITHREADDLG,標題爲“線程對話框”。

3. 爲對話框IDD_UITHREADDLG創建一個基於CDialog的類CUIThreadDlg。此對話框用於在子線程彈出,內容自便。

 

1. 在MultiThread6Dlg.h中加:

class CMultiThread6Dlg : public CDialog
{
 afx_msg void OnUiThread();
};

 

2. 在MultiThread6Dlg.cpp中加:

#include "MultiThread6Dlg.h"
#include "UIThread.h"
void CMultiThread6Dlg::OnUiThread()
{
 CWinThread *pThread=AfxBeginThread(RUNTIME_CLASS(CUIThread));
}

 

 

1.在UIThread.h中添加:

#include "UIThreadDlg.h"
class CUIThread : public CWinThread
{
 DECLARE_DYNCREATE(CUIThread)
protected:
 CUIThread();  // protected constructor used by dynamic creation

public:
 virtual BOOL InitInstance();
 virtual int ExitInstance();

protected:
 CUIThreadDlg m_dlg;
 virtual ~CUIThread();

 DECLARE_MESSAGE_MAP()
};

 

2.在UIThread.cpp中添加:

#include "UIThread.h"
IMPLEMENT_DYNCREATE(CUIThread, CWinThread)
CUIThread::CUIThread()
{

m_bAutoDelete = TRUE;
}

CUIThread::~CUIThread()
{
}

BOOL CUIThread::InitInstance()
{

/*CFrameWnd* wnd=new CFrameWnd;
wnd->Create(NULL,"UI Thread Window");
wnd->ShowWindow(SW_SHOW);
wnd->UpdateWindow();
m_pMainWnd=wnd;
return TRUE;*/
 m_dlg.Create(IDD_UITHREADDLG);
 m_dlg.ShowWindow(SW_SHOW);
 m_pMainWnd=&m_dlg;
 return TRUE;
}

int CUIThread::ExitInstance()
{
 m_dlg.DestroyWindow();
 return CWinThread::ExitInstance();
}

BEGIN_MESSAGE_MAP(CUIThread, CWinThread)
 //{{AFX_MSG_MAP(CUIThread)
  // NOTE - the ClassWizard will add and remove mapping macros here.
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章