> 文章列表 > 【MFC】多线程(22)

【MFC】多线程(22)

【MFC】多线程(22)

可以把线程看成是操作系统分配CPU时间的基本实体。系统为每一个线程分配一个CPU时间片(20毫秒左右),不停地在各个线程之间切换,某个线程只有在分配的时间片内才有对CPU的控制权。由于系统为每个线程划分的时间片很小,所以看上去好象是多个线程在同时运行。进程中的所有线程共享进程的虚拟地址空间,这意味着所有线程都可以访问进程的全局变量和资源。

MFC多线程

C++有几种开启多线程的方法,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 );

2、CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );

简而言之,第一种利用的是绑定线程函数,应用于工作线程,用于后台计算等(不带窗口),第二种则可以拥有自己的线程窗口,实现消息处理。

一、工作线程:

首先,定义线程函数,可以是全局函数或者静态函数,函数原型:

UINT Proc  (LPVOID lp)           //  lp 用于开启线程时,参数的传递

然后,开启线程:

::AfxBeginThread(Proc  ,(LPVOID)&lpData,NULL,NULL,NULL,NULL);

其中,lpData 用于传递给线程函数的参数, AfxBeginThread 创建线程后的返回值为CWinThread 的指针,可以利用该指针对线程进行一些处理(不推荐,除非有线程同步机制)。

与函数调用的最大不同是:函数调用是一种阻塞式的方式,也就是主调方需要等待被调函数的返回,而线程则是一种非阻塞的工作模式,开启新的工作线程后就执行后面的流程了。

在窗口程序中,往往将有大量耗时工作的任务交付给工作线程,这样主线程就不会被阻塞,可以及时处理用户的交互功能,避免 “卡死” 的现象。

开启的工作线程,可以从主线程中获取参数 LPVOID  ,由于线程共享进程所有的资源,所以可以按约定进行各种强制转换。

工作线程启动后,将自动执行线程函数(除非创建的时候指明需要手动),线程函数执行完毕后线程就终止了。

二、窗口线程

与工作线程不同的是,创建的新线程拥有自己的窗口。

首先,生成两个类,一个是继承于 CFrameWnd 的窗口类,另外一个是继承于CWinThread 的线程类

CMyThreadWnd 头文件:

#pragma once
#include "afxwin.h"#define WM_TEST WM_USER+1   //自定义消息,用于测试消息响应
class CMyThreadWnd :public CFrameWnd
{public:CMyThreadWnd(void);~CMyThreadWnd(void);DECLARE_MESSAGE_MAP()afx_msg LRESULT OnTest(WPARAM wParam,LPARAM lParam);
};

 CMyThreadWnd  CPP文件:

#include "stdafx.h"
#include "MyThreadWnd.h"CMyThreadWnd::CMyThreadWnd(void)
{
}CMyThreadWnd::~CMyThreadWnd(void)
{
}
BEGIN_MESSAGE_MAP(CMyThreadWnd, CFrameWnd)ON_MESSAGE(WM_TEST,OnTest)	
END_MESSAGE_MAP()LRESULT CMyThreadWnd::OnTest(WPARAM wParam,LPARAM lParam)
{LPCSTR p=(LPCSTR)wParam;CString data=(CString)p;CDC*dc = this->GetDC();CRect rc;this->GetClientRect(&rc);dc->PatBlt(0,0,rc.right,rc.bottom,WHITENESS);dc->TextOut(100,100,data);this->ReleaseDC(dc);return 0;
}

PS:消息循环的三个宏

DECLARE_MESSAGE_MAP()

BEGIN_MESSAGE_MAP(CMyThreadWnd, CFrameWnd)
END_MESSAGE_MAP() 

可以利用VS中类向导自动添加 (随便加一个消息处理函数就有了)

CMyThread  头文件:  

#pragma once
#include "afxwin.h"
#include "MyThreadWnd.h"
class CMyThread :public CWinThread
{DECLARE_DYNCREATE(CMyThread)
protected:CMyThreadWnd *wnd;
public:CMyThread(void);~CMyThread(void);virtual BOOL InitInstance();DECLARE_MESSAGE_MAP()afx_msg void OnTest(WPARAM wParam,LPARAM lParam);
};

动态创建宏,手动添加(否则报内存不够的错误) DECLARE_DYNCREATE

CMyThread  CPP文件: 

#include "stdafx.h"
#include "MyThread.h"IMPLEMENT_DYNCREATE(CMyThread, CWinThread);
CMyThread::CMyThread(void)
{wnd = new CMyThreadWnd();
}CMyThread::~CMyThread(void)
{
}BOOL CMyThread::InitInstance()
{// TODO: 在此添加专用代码和/或调用基类CWinThread::InitInstance();wnd->Create(NULL,"我的线程窗口");wnd->ShowWindow(SW_SHOW);wnd->UpdateWindow();return TRUE;
}
BEGIN_MESSAGE_MAP(CMyThread, CWinThread)ON_THREAD_MESSAGE(WM_TEST,OnTest)
END_MESSAGE_MAP()void CMyThread::OnTest(WPARAM wParam,LPARAM lParam)
{LPCSTR p=(LPCSTR)wParam;CString s;s.Format("获取消息:%s",p);::AfxMessageBox(s);::PostMessage(wnd->m_hWnd,WM_TEST,wParam,lParam);}

cpp文件中,IMPLEMENT_DYNCREATE    与头文件中动态创建相对应。

重写InitInstance 函数,该函数在创建线程时自动调用,返回值必须为TRUE,否则线程终止。在该函数中创建线程窗口,显示并更新窗口。OnTest函数用于响应主线程发送的 WM_TEST 消息,并且把该消息再次转发给 窗口。

调用方法,参考代码:

CWinThread *t=NULL;
t = ::AfxBeginThread(RUNTIME_CLASS(CMyThread));
LPCSTR data="Test Message";
PostThreadMessage(t->m_nThreadID,WM_TEST,(WPARAM)data,0);

由此可见,窗口线程与工作线程的区别。

然而!

工作线程也是可以拥有窗口的,不过该窗口会随工作线程的终止而销毁,另外,工作线程同样也可以处理消息。参考测试方法:

线程函数:

UINT Proc(LPVOID lp)
{ CMyWnd* Dlg = new CMyWnd();Dlg->Create(NULL, "线程窗口");Dlg->ShowWindow(SW_SHOW);Dlg->UpdateWindow();MSG msg;while(::GetMessage(&msg,0,0,0)){if(msg.message == WM_LBUTTONDBLCLK){::SendMessage(Dlg->m_hWnd,WM_LBUTTONDBLCLK,0,0);continue;}TranslateMessage(&msg);DispatchMessage(&msg);}return 0;
}

测试代码

  CWinThread* th3=::AfxBeginThread(abc3,NULL,NULL,NULL,NULL,NULL);::PostThreadMessage(th3->m_nThreadID,WM_LBUTTONDBLCLK,0,0);

  需要通过SendMessage给窗口发消息(不进消息队列)

当然,以上方法比窗口线程方法复杂多了!