4.消息传递方式
在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)是内核对象,内核对象的管理是操作系统管理的,具体你可以查查相关资料,我在此就不啰嗦了。
5.线程同步法
还可以通过线程同步来实现线程间通信。例如有两个线程,线程A写入数据,线程B读出线程A准备好的数据并进行一些操作。这种情况下,只有当线程A写好数据后线程B才能读出,只有线程B读出数据后线程A才能继续写入数据,这两个线程之间需要同步进行通信。
评论
查看更多