65.9K
CodeProject 正在变化。 阅读更多。
Home

WTL 的线程类

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (29投票s)

2006年5月30日

4分钟阅读

viewsIcon

87625

downloadIcon

1476

介绍了一组精简的 ATL/WTL 类,用于简化线程操作。

引言

虽然 ATL 和 WTL 为 Win32 的各种 HANDLE 对象提供了有用的包装类,但线程的创建和控制仍然需要直接使用 API 函数和句柄。诚然,这并非难事,但一个清晰的面向对象设计通常有助于避免多线程应用程序中的 bug。

我将要介绍的这个类集合对我来说非常有用。它包含了线程的句柄包装器,以及帮助实现工作线程和 GUI 线程的基类。

线程句柄包装器类

这些类没什么好说的。秉承 WTL 一贯的风格,CThreadT 是一个围绕线程句柄的模板包装类,它将大多数接受线程句柄的 Win32 API 函数作为方法暴露出来。

更方便的是,通常会使用模板实例化 CThreadCThreadHandleCThread 在销毁时会关闭线程句柄,而 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。
  • 方法 OpenGetPrioritySetPriorityGetExitCodeGetThreadTimesIsIOPendingResumeSuspendTerminateExit 都是相应 API 函数的包装器,它们的名称大多是去掉了“thread”一词。
  • 方法 Join 在线程句柄上执行 WaitForSingleObject

GUI 线程

GUI 线程与一般工作线程不同,它们拥有消息队列。这意味着我们可以使用 PostThreadMessage 向 GUI 线程发送消息。

模板类 CGuiThreadT 及其实例化 CGuiThreadCGuiThreadHandle,与 CThreadT 类类似,增加了一个 PostThreadMessage 方法来实现此目的。

通常,通过向 GUI 线程的消息队列发送 WM_QUIT 消息来使其退出。这可以通过 PostQuitMessage 方法完成。但是,这个方法 *不* 是对同名 API 的包装,因为后者会将 WM_QUIT 发送到调用线程,而这不总是同一个线程。

线程实现类

正如 ATL 和 WTL 经常区分对象句柄和其实例(请参阅 CWindowCWindowImpl),我为我的线程类也采用了相同的设计(尽管这两种情况并不完全可比)。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。在 InitializeThreadCleanupThread 方法中是安装这些的好地方。

示例

下面的示例展示了一个简单的 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 线程相关的部分。其他类(CThreadTCThreadImpl)也可以与“纯”ATL 一起工作。

结论

使用本文介绍的类集,可以为多线程应用程序实现更清晰、更面向对象的代码。类似于在其他 ATL/WTL 类中找到的模板设计,使其易于理解和集成。

修订历史

  • 06-30-2006
    • 原始文章。
  • 06-31-2006
    • CThreadT::Create 现在如果可能(取决于 _ATL_MIN_CRT)会调用 _beginthreadex
    • 添加了方法 CThreadT::Exit
© . All rights reserved.