WTL 的线程类






4.85/5 (29投票s)
2006年5月30日
4分钟阅读

87625

1476
介绍了一组精简的 ATL/WTL 类,用于简化线程操作。
引言
虽然 ATL 和 WTL 为 Win32 的各种 HANDLE
对象提供了有用的包装类,但线程的创建和控制仍然需要直接使用 API 函数和句柄。诚然,这并非难事,但一个清晰的面向对象设计通常有助于避免多线程应用程序中的 bug。
我将要介绍的这个类集合对我来说非常有用。它包含了线程的句柄包装器,以及帮助实现工作线程和 GUI 线程的基类。
线程句柄包装器类
这些类没什么好说的。秉承 WTL 一贯的风格,CThreadT
是一个围绕线程句柄的模板包装类,它将大多数接受线程句柄的 Win32 API 函数作为方法暴露出来。
更方便的是,通常会使用模板实例化 CThread
和 CThreadHandle
:CThread
在销毁时会关闭线程句柄,而 CThreadHandle
则不会。
创建线程
线程创建被实现为静态方法 CThreadT::Create
,它是 _beginthreadex
的包装器(如果设置了“最小化使用 CRT”选项,则为 CreateThread
)。
// ... CThread thread = CThread::Create( (LPTHREAD_START_ROUTINE) MyThreadProc, pParam ); // ...
CThread 和 CThreadHandle 的附加方法
CThreadT(HANDLE=NULL, DWORD=0)
将给定的线程句柄和线程 ID 包装到 CThread 实例中。- 还有一个复制构造函数,它将对给定句柄调用
DuplicateHandle
。 - 可以通过
GetHandle()
和GetId()
获取线程句柄和 ID。 - 方法
Open
、GetPriority
、SetPriority
、GetExitCode
、GetThreadTimes
、IsIOPending
、Resume
、Suspend
、Terminate
和Exit
都是相应 API 函数的包装器,它们的名称大多是去掉了“thread”一词。 - 方法
Join
在线程句柄上执行WaitForSingleObject
。
GUI 线程
GUI 线程与一般工作线程不同,它们拥有消息队列。这意味着我们可以使用 PostThreadMessage
向 GUI 线程发送消息。
模板类 CGuiThreadT
及其实例化 CGuiThread
和 CGuiThreadHandle
,与 CThreadT
类类似,增加了一个 PostThreadMessage
方法来实现此目的。
通常,通过向 GUI 线程的消息队列发送 WM_QUIT
消息来使其退出。这可以通过 PostQuitMessage
方法完成。但是,这个方法 *不* 是对同名 API 的包装,因为后者会将 WM_QUIT
发送到调用线程,而这不总是同一个线程。
线程实现类
正如 ATL 和 WTL 经常区分对象句柄和其实例(请参阅 CWindow
和 CWindowImpl
),我为我的线程类也采用了相同的设计(尽管这两种情况并不完全可比)。CThreadImpl
类为线程“实现”类提供了骨架。
从 CThreadImpl<T>
派生你的线程类,并实现 Run
方法。
class CWorkerThread : public CThreadImpl<CWorkerThread>
{
public:
DWORD Run()
{
// Do something useful...
return 0;
}
};
//
// In some other function, that is called from your main thread:
CWorkerThread* pThread = new CWorkerThread;
Run
的返回值是线程的退出代码,与标准的 Win32 ThreadProc
相同。
如果你创建一个 CWorkerThread
实例,它会立即开始运行。如果你想稍后启动它,可以向 CThreadImpl
的构造函数传递 CREATE_SUSPENDED
,然后在稍后调用 Resume
。
请注意,构造函数在创建线程中运行,因此一些线程初始化可能需要移到 Run
方法中。
示例
class CWorkerThread : public CThreadImpl<CWorkerThread> { public: CWorkerThread() : CThreadImpl<CWorkerThread>(CREATE_SUSPENDED) { } BOOL Initialize() { // Perform initialization. return TRUE; } DWORD Run() { if ( !Initialize() ) return 1; // Do something useful... return 0; } }; // // In some other function, that is called from your main thread: CWorkerThread* pThread = new CWorkerThread; pThread->Resume();
GUI 线程的实现
CGuiThreadImpl
类使用 WTL 的 CMessageLoop
类来管理消息循环。但是,由于 CAppModule
需要知道进程中的所有 CMessageLoop
,因此你必须在构造函数中传递指向你的 CAppModule
实例的指针。
要实现一个 GUI 线程,请从 CGuiThreadImpl
派生你的类,并(可选地)重写以下方法:
BOOL InitializeThread()
用于执行线程初始化。例如,这是创建窗口的好地方。返回FALSE
将停止线程。void CleanupThread(DWORD)
用于执行清理任务。DWORD
参数是消息循环的退出代码。
处理消息
你也可以使用标准的宏 BEGIN_MSG_MAP
将消息映射添加到你的线程类。但是,这些只会在消息不直接发送到窗口时(即 hWnd
参数为 NULL
)被调用。
请记住,你可以通过 CAppModule::GetMessageLoop()
访问线程的消息循环,因此你可以安装额外的 CMessageFilter
。在 InitializeThread
和 CleanupThread
方法中是安装这些的好地方。
示例
下面的示例展示了一个简单的 GUI 线程类,它创建一个计时器并响应 WM_TIMER
消息。
#include "Thread.h" class CTimerThread : public CGuiThreadImpl<CTimerThread> { BEGIN_MSG_MAP(CTimerThread) MESSAGE_HANDLER(WM_TIMER, OnTimer) END_MSG_MAP() private: UINT_PTR m_nTimerId; public: CTimerThread(CAppModule* pModule) : CGuiThreadImpl<CTimerThread>(pModule) { } BOOL InitializeThread() { m_nTimerId = ::SetTimer(NULL, 0, 1000, NULL); return (m_nTimerId != 0); } void CleanupThread(DWORD) { ::KillTimer(NULL, m_nTimerId); } LRESULT OnTimer(UINT, WPARAM, LPARAM, BOOL&) { ::MessageBeep(MB_ICONASTERISK); return 0; } };
“计时器线程”必须从主线程创建和停止。
class CMainFrame : ... { CTimerThread* m_pTimerThread; // ... LRESULT OnCreate(LPCREATESTRUCT) { // ... m_pTimerThread = new CTimerThread(&_Module); // ... } void OnDestroy() { // ... g_pTimerThread->PostQuitMessage(); g_pTimerThread->Join(); delete g_pTimerThread; // ... } };
使用这些类
所有线程类都包含在一个头文件 *Thread.h* 中,你可以通过文章开头提供的链接下载。为了方便使用这些类,只需在你的项目中包含 *Thread.h* 即可。
如果你想在仅使用 ATL 而不使用 WTL 的项目中使用这些类,你需要从代码中删除所有与 GUI 线程相关的部分。其他类(CThreadT
和 CThreadImpl
)也可以与“纯”ATL 一起工作。
结论
使用本文介绍的类集,可以为多线程应用程序实现更清晰、更面向对象的代码。类似于在其他 ATL/WTL 类中找到的模板设计,使其易于理解和集成。
修订历史
- 06-30-2006
- 原始文章。
- 06-31-2006
CThreadT::Create
现在如果可能(取决于_ATL_MIN_CRT
)会调用_beginthreadex
。- 添加了方法
CThreadT::Exit
。