电子常识
摘要:本文主要以MFC多线程为中心,分别对MFC多线程的实例、MFC多线程之间的通信展开的一系列研究,下面我们来看看原文。
进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。
线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。
每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进 程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也 较为广泛。
多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程, 操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常 活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。
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()成员函数一样,该函数也只适用于用户界面线程。
在Visual C++
6.0编程环境中,我们既可以编写C风格的32位Win32应用程序,也可以利用MFC类库编写C++风格的应用程序,二者各有其优缺点。
基于Win32的应用程序执行代码小巧,运行效率高,但要求程序员编写的代码较多,且需要管理系统提供给程序的所有资源;而基于MFC类库的应用程序可以快速建立起应用程序,类库为程序员提供了大量的封装类,而且DeveloperStudio为程序员提供了一些工具来管理用户源程序,其缺点是类库代码很庞大。由于使用类库所带来的快速、简捷和功能强大等优越性,因此除非有特殊的需要,否则VisualC++推荐使用MFC类库进行程序开发。
我们知道,MFC中的线程分为两种:用户界面线程和工作者线程。我们将分别举例说明。
用 MFC 类库编程实现工作者线程
例程5 MultiThread5
为了与Win32 API对照,我们使用MFC 类库编程实现例程3 MultiThread3。
建立一个基于对话框的工程MultiThread5,在对话框IDD_MULTITHREAD5_DIALOG中加入一个编辑框 IDC_MILLISECOND,一个按钮IDC_START,标题为“开始” ,一个进度条IDC_PROGRESS1;
打开 ClassWizard,为编辑框IDC_MILLISECOND添加int型变量m_nMilliSecond,为进度条IDC_PROGRESS1添加CProgressCtrl型变量m_ctrlProgress;
在MultiThread5Dlg.h文件中添加一个结构的定义: struct threadInfo
{
UINT nMilliSecond;
CProgressCtrl* pctrlProgress;
};
线程函数的声明:UINT ThreadFunc(LPVOID lpParam);
注意,二者应在类CMultiThread5Dlg 的外部。
在类CMultiThread5Dlg内部添加protected型变量:
CWinThread* pThread;
在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 类库编程实现用户界面线程
创建用户界面线程的步骤:
使用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()
};
重载函数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函数中的任务而不执行消息循环,你可以通过这种方法,完成一个工作者线程的功能。
一般而言,在一个应用程序中(即进程),一个线程往往不是孤立存在的,常常需要和其它线程通信,以执行特定的任务。如主线程和次线程,次线程与次线程,工作线程和用户界面线程等。这样,线程与线程间必定有一个信息传递的渠道。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的。线程间的通信涉及到4个问题:
(1) 线程间如何传递信息
(2) 线程之间如何同步,以使一个线程的活动不会破坏另一个线程的活动,以保证计算结果的正确合理
(3) 当线程间具有依赖关系时,如何调度多个线程的处理顺序
(4) 如何避免死锁问题
在windows系统中线程间的通信一般采用四种方式:全局变量方式、消息传递方式、参数传递方式和线程同步法。下面分别作介绍。
由于属于同一个进程的各个线程共享操作系统分配该进程的资源,故解决线程间通信最简单的一种方法是使用全局变量。对于标准类型的全局变量,我们建议使用volatile 修饰符,它告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。
实例演示
该实例采用全局变量来控制时间显示线程的显示格式,比较简单。
主要的代码如下:
.h头文件
//线程函数声明
DWORD WINAPIThreadProc(LPVOIDlpParam);
protected:
HANDLE m_hThread;//线程句柄
DWORD m_nThread;//线程ID
.cpp实现文件
volatileBYTE m_nShowFlag = 31;//定义全局变量,用于控制显示时间的格式。volatile 修饰符的作用是告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中。
//创建显示时间的线程,参数无
m_hThread =CreateThread(NULL,0,ThreadProc,NULL,0,&m_nThread);
//线程执行函数,用于实时显示时间,并按规定格式显示
DWORD WINAPIThreadProc(LPVOID lpParam)
{
while(m_nShowFlag)
{
CTime time;
CString strTime,strFormat;
time=CTime::GetCurrentTime();
strFormat = “%H:%M”;
if (m_nShowFlag&2)
{//日期
strFormat = “%Y-%m-%d” + strFormat;
}
if (m_nShowFlag&4)
{//秒钟
strFormat += “:%S”;
}
if (m_nShowFlag&8)
{//周数
strFormat += “%W”;
}
if (m_nShowFlag&16)
{//星期
strFormat += “%a”;
}
strTime=time.Format(strFormat);
::SetDlgItemText(AfxGetApp()-》m_pMainWnd-》m_hWnd,IDC_STATIC_TIME,strTime);
Sleep(100);
}
return 0;
}
运行效果:
工程源码下载地址:
http://download.csdn.net/detail/cbnotes/4962315
注意事项:
(1) 全局变量最好放在.CPP文件的起始处,而不要放在.h头文件中,否则将出现重复链接的编译错误。定义全局变量时最好显式初始化,默认初始值为零。
(2) 注意语句
::SetDlgItemText(AfxGetMainWnd()-》m_hWndIDC_STATIC_TIME,strTime);在VC6.0中可以通过,但在VC2008却出错,这是因为在VC2008中不支持AfxGetMainWnd()-》m_hWnd来获取HWND,但可以采AfxGetApp()-》m_pMainWnd-》m_hWnd来获取。所以上面的语句更改为:
::SetDlgItemText(AfxGetApp()-》m_pMainWnd-》m_hWnd,IDC_STATIC_TIME,strTime);
(3) 用全局变量方式来实现多线程的通信比较简单实用,单注意最好不要多个线程对它进行修改,否则将可能出错,这将在后面会具体讲解。
该方式是线程通信的官方标准方法,多数情况下,主线程创建子线程并让其子线程为其完成特定的任务,主线程在创建子线程时,可以通过传给线程函数的参数和其通信,三类创建线程的函数都支持参数的传递(哪三类?看前面的介绍吧!)。所传递的参数是一个32位的指针,该指针不但可以指向简单的数据,而且可以指向结构体或类等复杂的抽象数据类型。
实例演示
下面将分别简单演示三类创建线程时提供参数传递的方法。
主要的代码如下:
.h头文件
//线程函数声明
DWORD WINAPI ThreadFunc1(LPVOID lpParam);//线程函数
void ThreadFunc2(void *pArg); //线程函数
UINT ThreadFunc3(LPVOID lpParam);//线程函数
//全局函数
POINT GetRandPoint();//得到随机点的坐标
//结构体定义,用于向线程传递多个参数
struct threadInfo
{
HWND hWnd;//主窗口句柄
COLORREF clrPen;//画笔颜色
};
.cpp实现文件
//开始创建线程:创建个线程
void CMultThreadComm2Dlg::OnStart(void)
{
//线程:使用win32 API 创建:实时显示时间
m_hThread1 = CreateThread(NULL,0,ThreadFunc1,&m_stTime.m_hWnd,0,NULL);//
//线程:使用CRT 创建:随机画线
m_info.hWnd = m_hWnd;
m_info.clrPen =RGB(255,0,0);
_beginthread(ThreadFunc2,0,&m_info);
//线程:使用MFC线程函数创建:显示进度
m_pThread = AfxBeginThread(ThreadFunc3,&m_ctrlProgress);
}
//停止/开始
void CMultThreadComm2Dlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CString szTitle;
GetDlgItemText(IDC_BUTTON1,szTitle);
if (szTitle == “停止”)
{//停止
g_bRun = false;
SetDlgItemText(IDC_BUTTON1,“开始”);
}
else
{//开始
g_bRun = true;
OnStart();
SetDlgItemText(IDC_BUTTON1,“停止”);
}
}
//线程执行函数:实时显示当前的时间
DWORD WINAPI ThreadFunc1(LPVOID lpParam)
{
HWND *hWnd = (HWND*)lpParam;
//CWnd *pWnd = AfxGetApp()-》m_pMainWnd;//当没有传递参数时,可以用该api实现
CWnd *pWnd = CWnd::FromHandle(*hWnd);
char tmpbuf[128] ={‘0’};
time_t t;
while(g_bRun)
{
t = time(NULL);
strftime(tmpbuf,128,“%Y-%m-%d%a %I:%M:%S %p”,localtime(&t));
pWnd-》SetWindowText(tmpbuf);
Sleep(500);
}
return 0;
}
//线程执行函数:随机画线
void ThreadFunc2(void *pArg)
{
threadInfo *threadinfo= (threadInfo*)pArg;
CWnd *pWnd = CWnd::FromHandle(threadinfo-》hWnd);
CDC *pDC = pWnd-》GetDC();
CPen pen(PS_SOLID,2,threadinfo-》clrPen);
pDC-》SelectObject(&pen);
pDC-》SetROP2(R2_NOTXORPEN);
while(g_bRun)
{
POINT StartPos= GetRandPoint();
POINT EndPos = GetRandPoint();
//
CString str;
str.Format(“%d,%d : %d,%d\n”,StartPos.x,StartPos.y,EndPos.x,EndPos.y);
TRACE(str);
pDC-》MoveTo(StartPos);
pDC-》LineTo(EndPos);
Sleep(100);
pDC-》MoveTo(StartPos);
pDC-》LineTo(EndPos);
Sleep(100);
}
DeleteObject(pDC);
}
//线程执行函数:显示进度
UINT ThreadFunc3(LPVOIDlpParam)
{
CProgressCtrl *pProgress= (CProgressCtrl*)lpParam;
while(g_bRun)
{
pProgress-》StepIt();
Sleep(500);
}
return 0;
}
//得到随机点
POINT GetRandPoint()
{
POINT Point;
Point.x = rand()%439;
Point.y = rand()%208;
return Point;
}
运行效果:
工程源码下载地址:
http://download.csdn.net/detail/cbnotes/4984274
注意事项:
(1) 注意三类线程的创建方法和各自的线程函数的格式(返回类型各不一样)。
(2) 注意三类参数的传递:单个参数、多参数(结构体),复杂参数(类)。
(3) 采用参数传递方式进行线程间的通信只适用于主线程向从线程的通信。
(4) 不知道大家看出该程序的一个BUG没有(大家可以下载工程源码,编译并运行,可以很明显的发现。),就是线程二的随机画线线程,原意是随机画线并清除,但运行发现第一次画线时总是没有被清除掉,为什么?望大家动脑,知道的可以留言,大家一起学习和讨论!
在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。我们可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的。一个线程向另外一个线程发送消息是通过操作系统实现的。利用Windows操作系统的消息驱动机制,当一个线程发出一条消息时,操作系统首先接收到该消息,然后把该消息转发给目标线程,接收消息的线程必须已经建立了消息循环。该方式可以实现任意线程间的通信,所以是比较常见和通用的方式。
系统也提供了线程间发送消息的专用函数:PostThreadMessage()。用户先定义一个用户消息,在一个线程调用PostThreadMessage()函数,在消息接收线程响应该消息,大体和常用的小子响应处理差不多。只是消息映射为ON_THREAD_MESSAGE而不是ON_MESSAGE
如果线程有窗体,还可以使用通用的消息发送函数PostMessage()和SendMessage()这两个函数。注意这两则的区别,PostMessage()是异步函数,调用后函数立即返回,而SendMessage()是同步函数,要等相应的消息响应完成后才返回。
关于PostThreadMessage()、PostMessage()和SendMessage()函数介绍请参考MSDN,在此就不多吃一举了。
对于PostThreadMessage()的用法:
先定义一个用户消息,如:
#define WM_THREADMSG WMUSER+100
在需要发送消息的线程调用PostThreadMessage()函数,如:
::PostThreadMessage(idThread,WM_THREADMSG,parm1, parm2);
或者
m_pThread-》PostThreadMessage(WM_THREADMSG,parm1, parm2);
其中: idThread为消息接受线程的ID,parm1, parm2为要传递的参数,m_pThread消息接受线程指针。
在接受消息线程中(或消息处理线程中),先定义消息响应函数,如:
afx_msg void OnThreadMessage(WPARAM wParam,LPARAM lParam);
然后在消息映射表中添加该消息的映射,如:
ON_THREAD_MESSAGE(WM_THREADMSG,OnThreadMessage)
最后,实现该消息函数,如:
//显示消息处理函数
void XXXXThread::OnThreadMessage(WPARAM wParam,LPARAM lParam)
{
;//消息处理
}
/////////////////////////////////////////////////////////////////////////////////
对于PostMessage()/SendMessage()的用法:和上面的差不多,略有不同。
首先也是先定义一个用户消息,如:
#define WM_THREADMSG WMUSER+100
在需要发送消息的线程调用PostMessage()/SendMessage()函数,如:
::PostMessage(hWnd, WM_THREADMSG, parm1, parm2);//发送消息
或者
PWnd-》PostMessage(WM_THREADMSG, parm1, parm2);
或者
::SendMessage(hWnd, WM_THREADMSG, parm1, parm2);//发送消息
或者
PWnd-》SendMessage(WM_THREADMSG, parm1, parm2);
其中: hWnd为接受消息的窗口句柄,parm1, parm2为要传递的参数,pWnd消息接受窗口指针。
在接受消息线程中(或消息处理线程中),先定义消息响应函数,如:
afx_msg LRESULT OnMyMessage(WPARAM wParam,LPARAM lParam);
然后在消息映射表中添加该消息的映射,如:
ON_MESSAGE(WM_THREADMSG, OnMyMessage)
最后,实现该消息函数,如:
//消息处理函数
LRESULT XXXXWnd::OnMyMessage(WPARAM wParam,LPARAM lParam)
{
;//消息处理
return 0;
}
实例演示
该实例主要时计算正整数1-N的累加,并采用单独的线程来计算累积,并创建另一个用户界面线程来实时显示累加的进度,其中涉及到一些线程间的通信,该实例主要采用了消息的方式,并结合前面已经介绍的两种通信方式,比较具有演示和学习的价值。
主要源码:
主线程头文件:
DWORD WINAPI ThreadFunc(LPVOIDlpParam);//线程函数
protected:
HANDLE m_hThread;//线程句柄
CProgressThread *m_pThread;//用户界面线程句柄
DWORD m_nThreadID;//线程ID号
主线程实现文件:
//开始计算
void CMultThreadComm3Dlg::OnBnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);//更新数据
g_bStop = false; //重置
m_hThread = CreateThread(NULL,0,ThreadFunc,&m_nRange,0,NULL);//创建计算线程
GetDlgItem(IDC_BUTTON1)-》EnableWindow(FALSE);//防止重复计算
SetDlgItemText(IDC_STATIC_RESULT,“计算中。。。”);
}
//结果显示:两种结果:,正常,,被中途中止
LRESULT CMultThreadComm3Dlg::OnResult(WPARAMwParam,LPARAMlParam)
{
if (wParam == 1)
{//被中途中止
SetDlgItemText(IDC_STATIC_RESULT,“被中止啦!”);
}
else
{//正常结束
SetDlgItemInt(IDC_STATIC_RESULT,lParam);
}
GetDlgItem(IDC_BUTTON1)-》EnableWindow(TRUE);//使能下次计算按钮
CloseHandle(m_hThread);//关闭线程句柄,注意一定要关闭它
return 0;
}
//线程执行函数:计算
DWORD WINAPI ThreadFunc(LPVOIDlpParam)
{
UINT *pRange =(UINT*)lpParam;//得到参数值
long nResult= 0L;//计算结果值
bool bStop = false;//标识是否中途被中止
CProgressThread *m_pThread= (CProgressThread*)AfxBeginThread(RUNTIME_CLASS(CProgressThread));//创建显示进程
m_pThread-》PostThreadMessage(WM_PROGRESS,0,*pRange);//传递参数,进度条的范围
for(int i=0;i《=*pRange;i++)//开始计算
{
if (g_bStop)
{//中途取消了
bStop = true;
break;//退出循环
}
nResult += i;
m_pThread-》PostThreadMessage(WM_PROGRESS,1,i);//进度
Sleep(10);//为了演示效果,特意延时
}
//完成
::PostMessage(AfxGetApp()-》m_pMainWnd-》m_hWnd,WM_RESULT,bStop,nResult);//显示结果
m_pThread-》PostThreadMessage(WM_PROGRESS,2,0);//结束进度
return 0;
}
累加线程头文件:
CProgressDlg *m_pProgressDlg;//进度条对话框
unsigned int m_nRange;//进度条的范围
累加线程实现文件:
BOOL CProgressThread::InitInstance()
{
// TODO: 在此执行任意逐线程初始化
//创建非模式进度显示对话框
m_pProgressDlg = newCProgressDlg();
m_pProgressDlg-》Create(IDD_DIALOG1);
m_pProgressDlg-》ShowWindow(SW_SHOW);
return TRUE;
}
BEGIN_MESSAGE_MAP(CProgressThread, CWinThread)
ON_THREAD_MESSAGE(WM_PROGRESS, &CProgressThread::OnThreadMsg)
END_MESSAGE_MAP()
// CProgressThread 消息处理程序
//线程消息处理函数
void CProgressThread::OnThreadMsg(WPARAM wParam,LPARAM lParam)
{
if (wParam == 0)
{//初始化进度条
m_nRange = lParam;
m_pProgressDlg-》m_ProgressCtrl.SetRange(0,lParam);
}
else if (wParam == 1)
{//显示进度
m_pProgressDlg-》m_ProgressCtrl.SetPos(lParam);//显示进度
CString str;
str.Format(“%d%%”,int((float)lParam/m_nRange*100));
m_pProgressDlg-》m_stValue.SetWindowText(str);//显示百分数
str.Format(“计算处理中,请稍等。。。 %d/%d cbNotes”,lParam,m_nRange);//显示实时的当前操作
m_pProgressDlg-》SetWindowText(str);
}
else
{//完成,退出进度条
m_pProgressDlg-》CloseDlg();//结束进程对话框
AfxEndThread(0);//终止本线程,也可以使用PostQuitMessage(0);
}
}
进度条对话框类
//系统消息处理
void CProgressDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
if (nID == SC_CLOSE)//关闭
{//拦截关闭按钮消息
if (AfxMessageBox(“确定要【终止】本次计算吗?”,MB_YESNO|MB_APPLMODAL|MB_ICONQUESTION|MB_DEFBUTTON2)==IDYES)
{
g_bStop = true;//中途取消操作,结束计算线程。
}
return;
}
CDialog::OnSysCommand(nID, lParam);
}
//关闭窗口
void CProgressDlg::CloseDlg(void)
{
OnCancel();
}
//重载OnCancel():注意非模式对话框的退出
void CProgressDlg::OnCancel()
{
// TODO: 在此添加专用代码和/或调用基类
DestroyWindow();//
//CDialog::OnCancel();
}
void CProgressDlg::PostNcDestroy()
{
// TODO: 在此添加专用代码和/或调用基类
CDialog::PostNcDestroy();
delete this;
}
运行结果:
工程源码下载地址:
http://download.csdn.net/detail/cbnotes/5007011
注意事项:
(1) 注意线程消息的映射方式:
ON_THREAD_MESSAGE(WM_PROGRESS, &CProgressThread::OnThreadMsg)
和一般的窗口消息映射不同,不要搞错了。
(2) 注意用户界面线程的退出,计算完后要记得结束该线程,有专门的函数:
AfxEndThread(0),也可以使用PostQuitMessage(0)。这两种方式都是比较安全的。不推荐强制中止线程,否则会出现意想不到的问题。
(3) 该实例还实现了执行过程按退出按钮时的处理方法,采用了全局变量的
方式来控制计算线程的中途退出,我自认为是一种比较好的控制方式。我开始采用强制中止的方式会出现消息延后的现象(就是在退出按钮的处理函数里向主线程发送消息postmessage),一直没有找到原因,有兴趣的朋友可以试试其它的退出方式供大家讨论学习。
(4) 注意非模式对话框退出的处理方法,和模式对话框的退出不一样的。
(5)用CreateThread()函数创建线程将返回一个线程句柄,通过该句柄你可以控制和操作该线程,当你不用时可以一创建该线程后就关闭该句柄,有专门的函数CloseHandle()。关闭句柄并代表关闭线程,只是你不能在外部控制该线程(比如,提前结束,更改优先级等)。在线程结束后,系统将自动清理线程资源,但并不自动关闭该句柄,所以线程结束后要记得关闭该句柄,你可能要问为什么你这么强调要关闭该句柄,是因为该句柄(HANDLE)是内核对象,内核对象的管理是操作系统管理的,具体你可以查查相关资料,我在此就不啰嗦了。
还可以通过线程同步来实现线程间通信。例如有两个线程,线程A写入数据,线程B读出线程A准备好的数据并进行一些操作。这种情况下,只有当线程A写好数据后线程B才能读出,只有线程B读出数据后线程A才能继续写入数据,这两个线程之间需要同步进行通信。
全部0条评论
快来发表一下你的评论吧 !