警告
如果要在 MFC 程序中产生一个线程, 而该线程将调用 MFC 函数或使用 MFC 的任何数据,那么你必须以 AfxBeginThread或CWinThread::CreateThread来产生这些线程。
worker 线程和 GUI 线程
Win32 两者一般而言都是以 CreateThread 或 _beginthreadex 开始其生命. 如果线程调用 GetMessage 或 CreateWindow 之类函数, 消息队列便会产生, 而 worker 线程也就摇身一变成了 GUI 线程(或称为 UI 线程)
一、在MFC中启动一个Worker线程
AfxBeginThread的第一种形式用来启动一个worker线程
CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc, // 线程函数
LPVOID pParam, // 线程参数
int nPriority = THREAD_PRIORITY_NORMAL, // 优先权
UINT nStackSize = 0, // 堆栈大小, 0表示默认值
DWORD dwCreateFlags = 0, // 此值必须为 0 或 CREATE_SUSPENDED
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL // CreateThread 所需的安全属性
);
如果失败, 传回NULL; 否则传回一个指针, 指向新产生出来的CWinThread对象。
1. 以 AfxBeginThread() 产生 worker 线程
#include <afxwin.h>
CWinApp TheApp;
UINT ThreadFunc(LPVOID);
int main()
{
for (int i=0; i<5; i++)
{
if (AfxBeginThread( ThreadFunc, (LPVOID)i ))
printf("Thread launched %d\n", i);
}
// Wait for the threads to complete.
Sleep(2000);
return 0;
}
UINT ThreadFunc(LPVOID n)
{
for (int i=0;i<10;i++)
printf("%d%d%d%d%d%d%d%d\n",n,n,n,n,n,n,n,n);
return 0;
}
2. 安全地使用AfxBeginThread的传回值
默认情况下, 当线程结束时, CWinThread 对象会自动被删除, 这是因为 MFC 安插了它自己的线程启动函数, 并且更在你自己的线程函数之前, 于是可以承担清理工作。用以删除 CWinThread 对象的那个清理函数,同时也关闭了线程的 handle. 当线程结束其生命时, 它的 handle也就被关闭了。如果从线程启动到结束所花费的时间很短, CWinThread 对象可能在 AfxBeginThread() 返回时就已经被砍了。在这种情况下, 任何操作只要触及线程的 handle, 就会让你的程序当掉。
CWinThread 中有一个成员变量 m_bAutoDelete, 这个参数可以阻止CWinThread 对象被自动删除, 那么你就得自己删除之. 为了能够设定此变量而不产生一个 race condition, 你必须先以挂起状态产生线程。不用调用 CloseHandle 关闭线程的handle, CWinThread的析构函数会自动完成此事, 不管 m_bAutoDelete 是否被设立。
#include <afxwin.h>
CWinApp TheApp;
UINT ThreadFunc(LPVOID);
int main()
{
CWinThread* pThreads[5];
for (int i=0; i<5; i++)
{
pThreads[i] = AfxBeginThread(
ThreadFunc,
(LPVOID)i,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED // 挂起线程
);
ASSERT(pThreads[i]);
pThreads[i]->m_bAutoDelete = FALSE; // 设置为不自动删除
pThreads[i]->ResumeThread(); // 运行线程
printf("Thread launched %d\n", i);
}
for (i=0; i<5; i++)
{
WaitForSingleObject(pThreads[i]->m_hThread, INFINITE); // 等待线程结束
delete pThreads[i]; // 删除线程对象
}
return 0;
}
UINT ThreadFunc(LPVOID n)
{
for (int i=0;i<10;i++)
printf("%d%d%d%d%d%d%d%d\n",n,n,n,n,n,n,n,n);
return 0;
}
3. 对CWinThread进行subclassing
#include <afxwin.h>
CWinApp TheApp;
class CUserThread : public CWinThread
{
public: // Member functions
CUserThread(AFX_THREADPROC pfnThreadProc);
static UINT ThreadFunc(LPVOID param);
public: // Member data
int m_nStartCounter;
private: // The "real" startup function
virtual void Go();
};
CUserThread::CUserThread(AFX_THREADPROC pfnThreadProc):CWinThread(pfnThreadProc, NULL) // Undocumented constructor
{
m_bAutoDelete = FALSE;
// Set the pointer to the class to be the startup value.
// m_pThreadParams is undocumented,
// but there is no work-around.
m_pThreadParams = this;
}
int main()
{
CUserThread* pThreads[5];
for (int i=0; i<5; i++)
{
// Pass our static member as the startup function第 10 章 ■ MFC 中的线程 287
pThreads[i] = new CUserThread( CUserThread::ThreadFunc );
// Set the appropriate member variable
pThreads[i]->m_nStartCounter = i;
// Start the thread in motion
VERIFY( pThreads[i]->CreateThread() );
printf("Thread launched %d\n", i);
}
for (i=0; i<5; i++)
{
WaitForSingleObject(pThreads[i]->m_hThread, INFINITE);
delete pThreads[i];
}
return 0;
}
// static
UINT CUserThread::ThreadFunc(LPVOID n)
{
CUserThread* pThread = (CUserThread*)n;
pThread->Go();
return 0;
}
void CUserThread::Go()
{
int n = m_nStartCounter;
for (int i=0;i<10;i++)
printf("%d%d%d%d%d%d%d%d\n",n,n,n,n,n,n,n,n);
}
二、在MFC中启动一个UI线程
AfxBeginThread第二版本
CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass, // 指向一个派生自CWinThread的runtime class 类
int nPriority = THREAD_PRIORITY_NORMAL,
UINT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
AfxBeginThread传回 NULL 表示失败。否则, 它会传回一个指针, 指向新的 CWinThread 对象。
CWinThread 提供了一堆多样化的虚函数, 你可以改写之以帮助消息的处理、线程的启动(startup)和清理(cleanup), 以及异常情况的处理(exception handling)。这些虚函数列举如下:
InitInstance
ExitInstance
OnIdle
PreTranslateMessage
IsIdleMessage
ProcessWndProcException
ProcessMessageFilter
Run
默认情况下只要改写 InitI nstance(),MFC就会开启一个消息循环。
1. 以 ClassWizard 产生一个 UI 线程
本例之中, ClassWizard 会为我们产生两个文件, 分别是 DemoThread.cpp和DemoThread.h,并把它们加到项目之中。在 DemoThread.cpp 中, 你会发现 ClassWizard 已经帮我们产生了 InitInstance 和 ExitInstance, 并为该线程产生出最上层的消息映射表(message map)。
class CDemoThread : public CWinThread
{
DECLARE_DYNCREATE(CDemoThread)
protected:
CDemoThread(); // protected constructor used by dynamic creation
// Attributes
public:
// Operations
public:
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CDemoThread)
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
//}}AFX_VIRTUAL
// Implementation
protected:
virtual ~CDemoThread();
// Generated message map functions
//{{AFX_MSG(CDemoThread)
// NOTE - the ClassWizard will add and remove member functions here.
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
在类声明一开始处, 你会看到 DECLARE_DYNCREATE() 宏的使用。这个宏实现出运行时类型识别(RTTI)和动态生成(Dynamic Creation), 同时也是让 CD emoThread 与 AfxBeginT hread() 合作的关键。在 DemoThread.cpp中你还会看到IMP LEMENT_DYNCREATE()宏. DECLARE_xxx 和IMPLEMENT_xxx 两宏总是成双成对地被使用。
2. 启动 UI 线程
一旦 wizard 产生出线程类以及 DYN CREATE 宏,你就应该准备使用AfxBeginThread()了。下面是个范例:
CDemoThread* pThread = (CDemoThread*)AfxBeginThread( RUNTIME_CLASS(CDemoThread) );
其中作为参数的,是一个数据结构(即 CRuntimeClass 对象), 允许 MFC 在运行时产生该类的一个实体。就像 wo rker 线程的情况一样,当你以 AfxBeginThread 产生一个 UI 线程时, 你就让这个函数去配置数据结构并传回。你也可以在你的构造函数中设定 m_AutoDelete为 FALSE 。这一次会比较容易些, 因为你已经派生了这一类, 并且构造函数将被AfxBeginThread调用。一旦线程开始执行, MFC 将调用你的 InitInstance 成员函数, 并进入消息循环中。你的 CWinThread 派生类的消息映射表(message map)将被作为消息派送的指引。