Chinaunix首页 | 论坛 | 博客
  • 博客访问: 413335
  • 博文数量: 95
  • 博客积分: 5001
  • 博客等级: 大校
  • 技术积分: 1030
  • 用 户 组: 普通用户
  • 注册时间: 2007-05-13 11:43
文章分类

全部博文(95)

文章存档

2007年(95)

我的朋友

分类: C/C++

2007-07-29 23:24:36

五、MFC对多线程编程的支持

  MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。

   工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的 用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执 行任务。

  在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:    

1、CWinThread* AfxBeginThread(AFX_THREADPROC        pfnThreadProc, 
                             LPVOID                pParam, 
                             
int                   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()成员函数一样,该函数也只适用于用户界面线程。

六、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; 

  3. 在MultiThread5Dlg.h文件中添加一个结构的定义:

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

  线程函数的声明:

UINT ThreadFunc(LPVOID lpParam);

  注意,二者应在类CMultiThread5Dlg的外部。  

  在类CMultiThread5Dlg内部添加protected型变量:   

CWinThread* pThread;

  4. 在MultiThread5Dlg.cpp文件中进行如下操作:定义公共变量:

threadInfo Info;

  双击按钮IDC_START,添加相应消息处理函数:   

void CMultiThread5Dlg::OnStart()
{
    
// TODO: Add your control notification handler code here

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

  在函数BOOL CMultiThread3Dlg::OnInitDialog()中添加语句:


    ……
    
// TODO: Add extra initialization here 

    m_ctrlProgress.SetRange(0,99); 
    m_nMilliSecond
=10

    UpdateData(FALSE); 
    
return TRUE;  // return TRUE  unless you set the focus to a control 

}

  添加线程处理函数:

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 类库编程实现用户界面线程

  创建用户界面线程的步骤:

  1. 使用ClassWizard创建类CWinThread的派生类(以CUIThread类为例)

class CUIThread : public CWinThread
{
    DECLARE_DYNCREATE(CUIThread)
protected
:
    CUIThread();           
//
 protected constructor used by dynamic creation
    
// Attributes

public:
    
// Operations

public:
    
//
 Overrides
    
//
 ClassWizard generated virtual function overrides
    
//{{AFX_VIRTUAL(CUIThread)

public:
    
virtual
 BOOL InitInstance();
    
virtual int
 ExitInstance();
    
//
}}AFX_VIRTUAL
    
// Implementation

protected:
    
virtual ~
CUIThread();
    
//
 Generated message map functions
    
//
{{AFX_MSG(CUIThread)
    
//
 NOTE - the ClassWizard will add and remove member functions here.
    
//}}AFX_MSG

    DECLARE_MESSAGE_MAP()
}
;

  2. 重载函数InitInstance()和ExitInstance()。

BOOL CUIThread::InitInstance()
{
    CFrameWnd
* wnd=new
 CFrameWnd;
    wnd
->Create(NULL,"UI Thread Window"
);
    wnd
->
ShowWindow(SW_SHOW);
    wnd
->
UpdateWindow();
    m_pMainWnd
=
wnd;
    
return
 TRUE;
}

  创建新的用户界面线程

void CUIThreadDlg::OnButton1()
{
    CUIThread
* pThread=new
 CUIThread();
    pThread
->
CreateThread();
}

  请注意以下两点:

  A、在UIThreadDlg.cpp的开头加入语句:

#include "UIThread.h"

  B、把UIThread.h中类CUIThread()的构造函数的特性由 protected 改为 public。

   用户界面线程的执行次序与应用程序主线程相同,首先调用用户界面线程类的InitInstance()函数,如果返回TRUE,继续调用线程的Run ()函数,该函数的作用是运行一个标准的消息循环,并且当收到WM_QUIT消息后中断,在消息循环过程中,Run()函数检测到线程空闲时(没有消 息),也将调用OnIdle()函数,最后Run()函数返回,MFC调用ExitInstance()函数清理资源。

  你可以创 建一个没有界面而有消息循环的线程,例如:你可以从CWinThread派生一个新类,在InitInstance函数中完成某项任务并返回FALSE, 这表示仅执行InitInstance函数中的任务而不执行消息循环,你可以通过这种方法,完成一个工作者线程的功能。   

例程6 MultiThread6

  1. 建立一个基于对话框的工程MultiThread6,在对话框IDD_MULTITHREAD6_DIALOG中加入一个按钮IDC_UI_THREAD,标题为“用户界面线程” 

  2. 右击工程并选中“New Class…”为工程添加基类为CWinThread派生线程类CUIThread。 

  3. 给工程添加新对话框IDD_UITHREADDLG,标题为“线程对话框”。 

  4. 为对话框IDD_UITHREADDLG创建一个基于CDialog的类CUIThreadDlg。使用ClassWizard为CUIThreadDlg类添加WM_LBUTTONDOWN消息的处理函数OnLButtonDown,如下:

void CUIThreadDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
    AfxMessageBox(
"You Clicked The Left Button!"
);
    CDialog::OnLButtonDown(nFlags, point);
}

  5. 在UIThread.h中添加 #include "UIThreadDlg.h"

  并在CUIThread类中添加protected变量CUIThread m_dlg:

class CUIThread : public CWinThread
{
    DECLARE_DYNCREATE(CUIThread)
protected
:
    CUIThread();           
//
 protected constructor used by dynamic creation
    
// Attributes

public:
    
// Operations

public:
    
//
 Overrides
    
//
 ClassWizard generated virtual function overrides
    
//{{AFX_VIRTUAL(CUIThread)

public:
    
virtual
 BOOL InitInstance();
    
virtual int
 ExitInstance();
    
//
}}AFX_VIRTUAL
    
// Implementation

protected:
    CUIThreadDlg m_dlg;
    
virtual ~
CUIThread();
    
//
 Generated message map functions
    
//
{{AFX_MSG(CUIThread)
    
//
 NOTE - the ClassWizard will add and remove member functions here.
    
//}}AFX_MSG

    DECLARE_MESSAGE_MAP()
}
;

6. 分别重载InitInstance()函数和ExitInstance()函数:

BOOL CUIThread::InitInstance()
{
    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();
}

7. 双击按钮IDC_UI_THREAD,添加消息响应函数:

void CMultiThread6Dlg::OnUiThread()
{
    CWinThread 
*pThread=
AfxBeginThread(RUNTIME_CLASS(CUIThread));
}

  并在MultiThread6Dlg.cpp的开头添加:

#include "UIThread.h"

  好了,编译并运行程序吧。每单击一次“用户界面线程”按钮,都会弹出一个线程对话框,在任何一个线程对话框内按下鼠标左键,都会弹出一个消息框。

七、线程间通讯

  一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程和次要线程间要进行通信。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的,下面将进行说明。  

  1、使用全局变量进行通信  

   由于属于同一个进程的各个线程共享操作系统分配该进程的资源,故解决线程间通信最简单的一种方法是使用全局变量。对于标准类型的全局变量,我们建议使用 volatile 修饰符,它告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。如果线程间所需传递的信息较复杂,我们可以定义一个 结构,通过传递指向该结构的指针进行传递信息。    

  2、使用自定义消息  

  我们可以在一个线程的执行 函数中向另一个线程发送自定义的消息来达到通信的目的。一个线程向另外一个线程发送消息是通过操作系统实现的。利用Windows操作系统的消息驱动机 制,当一个线程发出一条消息时,操作系统首先接收到该消息,然后把该消息转发给目标线程,接收消息的线程必须已经建立了消息循环。   

例程7 MultiThread7

   该例程演示了如何使用自定义消息进行线程间通信。首先,主线程向CCalculateThread线程发送消息WM_CALCULATE, CCalculateThread线程收到消息后进行计算,再向主线程发送WM_DISPLAY消息,主线程收到该消息后显示计算结果。  

   1. 建立一个基于对话框的工程MultiThread7,在对话框IDD_MULTITHREAD7_DIALOG中加入三个单选按钮IDC_RADIO1, IDC_RADIO2,IDC_RADIO3,标题分别为1+2+3+4+......+10,1+2+3+4+......+50,1+2+3+4 +......+100。加入按钮IDC_SUM,标题为“求和”。加入标签框IDC_STATUS,属性选中“边框”; 

  2. 在MultiThread7Dlg.h中定义如下变量:

protected:
    
int nAddend;

  代表加数的大小。  

  分别双击三个单选按钮,添加消息响应函数:

void CMultiThread7Dlg::OnRadio1()
{
    nAddend
=10
;
}

void CMultiThread7Dlg::OnRadio2()
{
    nAddend
=50
;
}

void CMultiThread7Dlg::OnRadio3() 
{
    nAddend
=100
;
}

  并在OnInitDialog函数中完成相应的初始化工作:

BOOL CMultiThread7Dlg::OnInitDialog()
{
    ……
    ((CButton
*)GetDlgItem(IDC_RADIO1))->
SetCheck(TRUE);
    nAddend
=10
;
    ……
}

  在MultiThread7Dlg.h中添加:

#include "CalculateThread.h"
#define WM_DISPLAY WM_USER+2
class CMultiThread7Dlg : public CDialog
{
    
// Construction

public:
    CMultiThread7Dlg(CWnd
* pParent = NULL); // standard constructor

    CCalculateThread* m_pCalculateThread;
    ……
protected
:
    
int
 nAddend;
    LRESULT OnDisplay(WPARAM wParam,LPARAM lParam);
    ……
}

  在MultiThread7Dlg.cpp中添加:

BEGIN_MESSAGE_MAP(CMultiThread7Dlg, CDialog)
    ……
    ON_MESSAGE(WM_DISPLAY,OnDisplay)
END_MESSAGE_MAP()
LRESULT CMultiThread7Dlg::OnDisplay(WPARAM wParam,LPARAM lParam)
{
    
int nTemp=(int
)wParam;
    SetDlgItemInt(IDC_STATUS,nTemp,FALSE);
    
return 0
;
}

  以上代码使得主线程类CMultiThread7Dlg可以处理WM_DISPLAY消息,即在IDC_STATUS标签框中显示计算结果。

  3. 双击按钮IDC_SUM,添加消息响应函数:

void CMultiThread7Dlg::OnSum()
{
    m_pCalculateThread
=(CCalculateThread*
)AfxBeginThread(RUNTIME_CLASS(CCalculateThread));
    Sleep(
500
);
    m_pCalculateThread
->
PostThreadMessage(WM_CALCULATE,nAddend,NULL);
}

  OnSum()函数的作用是建立CalculateThread线程,延时给该线程发送WM_CALCULATE消息。

  4. 右击工程并选中“New Class…”为工程添加基类为 CWinThread 派生线程类 CCalculateThread。

  在文件CalculateThread.h 中添加

#define WM_CALCULATE WM_USER+1
class CCalculateThread : public CWinThread
{
    ……
protected
:
    afx_msg LONG OnCalculate(UINT wParam,LONG lParam);
    ……
}
 
  在文件CalculateThread.cpp中添加

LONG CCalculateThread::OnCalculate(UINT wParam,LONG lParam)
{
    
int nTmpt=0
;
    
for(int i=0;i<=(int)wParam;i++
)
    
{
        nTmpt
=nTmpt+
i;
    }

    Sleep(
500);
    ::PostMessage((HWND)(GetMainWnd()
->
GetSafeHwnd()),WM_DISPLAY,nTmpt,NULL);
    
return 0
;
}

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

    ON_THREAD_MESSAGE(WM_CALCULATE,OnCalculate)
    
//和主线程对比,注意它们的区别

END_MESSAGE_MAP()

  在CalculateThread.cpp文件的开头添加一条:

#include "MultiThread7Dlg.h"

   以上代码为 CCalculateThread 类添加了 WM_CALCULATE 消息,消息的响应函数是 OnCalculate,其功能是根据参数 wParam 的值,进行累加,累加结果在临时变量nTmpt中,延时0.5秒,向主线程发送WM_DISPLAY消息进行显示,nTmpt作为参数传递。 
  编译并运行该例程,体会如何在线程间传递消息。
阅读(1981) | 评论(0) | 转发(0) |
0

上一篇: VC多线程编程【1】

下一篇:vc串口

给主人留下些什么吧!~~