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

CWorkerThread 和 IWorkerThreadClient – 深入了解

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.67/5 (8投票s)

2006年7月28日

Ms-PL

5分钟阅读

viewsIcon

42460

downloadIcon

538

本教程介绍如何使用 ATL7 线程类 CWorkerThread 及其相关的辅助类 CRTThreadTraits、IWorkerThreadClient 等。此外,它还介绍了一个可用于 CPU 密集型应用程序的通用日志记录组件。

Sample image

引言

本文旨在深入理解 ATL7 的 CWorkerThread 类及其用法。我一直在寻找一个可以复制代码并粘贴到我当前项目中的示例。不幸的是,我甚至无法通过 Google 找到一个。这就是本文的诞生原因。此外,这个线程类的使用方式并不那么直接,它确实需要比 MSDN 中提供更多的文档。

这个类的使用方式有点令人困惑。作为 MFC 开发人员,我们都非常清楚 CWinThread 类的用法。同样,在这里我们可能会认为 CWorkerThread 是要在我们的客户端类中使用的顶层主类。但事实并非如此。

包含的示例项目基本上是一个通用的日志记录组件,可用于任何应用程序的日志记录。该组件的实现方式对于处理密集型应用程序并需要在不影响核心处理时间的情况下记录大量数据至关重要。在我的案例中,该应用程序处理数百万条记录,平均完成时间约为 4 天。在处理过程中,每当出现错误时,都需要转储大约半兆字节大小的数据。如果它是同步日志记录,就会影响整体性能。所以我将其设为异步。为此,该组件将内容记录到一个内存队列中。一个单独的线程(CWorkerThread)轮询该队列并将数据转储到指定的辅助存储中。

CWorkerThread 类如何使用

这个类的使用方式有点令人困惑。作为 MFC 开发人员,我们都非常清楚 CWinThread 类的用法。同样,在这里我们可能会认为 CWorkerThread 是要在我们的客户端类中使用的顶层主类。但事实并非如此。

  1. 在这里,我们首先必须实现 IWorkerThreadClient 接口,并在实现类中创建一个 CWorkerThread 成员变量。
  2. 然后在 IWorkerThreadClient 实现类的构造函数中
    • 首先,调用 CWorkerThread 成员变量的 Initialize() 方法。
    • 然后,调用 CWorkerThread 成员变量的 AddHandle() 方法。AddHandle() 方法有两个参数:
      • 用于同步的事件句柄。
      • 线程回调函数指针(不完全是函数指针,而是 IWorkerThreadClient 派生类指针本身;IWorkerThreadClient 接口有一个名为 Execute() 的方法作为回调函数)。

      (我们也可以使用 Addtimer() 方法而不是 AddHandle()。但示例项目使用 AddHandle() 方法并信号内核对象来控制回调函数的调用。Addtimer() 会定期调用回调 Execute() 函数,而不是等待内核对象被信号。)

  3. 然后在客户端类中,创建一个 IWorkerThreadClient 实现类的实例,并调用 ResumeThread() 方法来启动线程。
  4. 调用 ResumeThread() 后,内核对象将被信号,并且 IWorkerThreadClient::Execute() 将被调用,直到内核对象进入非信号状态。通过调用 IWorkerThreadClient::FreezeThread() 方法可以改变内核对象的状态。
  5. 最后,要终止线程,请调用 CWorkerThread::Shutdown()。这可以在 IWorkerThreadClient 实现类的析构函数中完成。

理论部分到此为止。现在是时候动手编码了。正如上面提到的,我在这里使用了一个日志记录组件来实现 CWorkerThread。该组件有一个名为 CLogManager 的 co class,应用程序可以使用它进行日志记录。

CWorkerThread 在 LogManager 示例项目中的用法

在示例项目中,CLogManager 是客户端类,它使用一个单独的线程来异步地将日志数据序列化到辅助存储。CLogManager 实现为 ATL COM 类,而这个单独的线程使用 CWorkerThreadIWorkerThreadClient 实现。

  1. 如上所述,我们首先必须实现 IWorkerThreadClient 接口。
  2. 在示例项目中,CSerializerIWorkerThreadClient 实现类。然后,在派生于 IWorkerThreadClientCSerializer 类中创建一个 CWorkerThread 成员变量。

    CWorkerThread<CRTThreadTraits> m_thread;

    如上所示,CWorkerThread 是一个模板类,其参数类型为 CRTThreadTraits。它可以是 CRTThreadTraitsWin32ThreadTraits。如果线程使用任何 C 运行时函数,则必须是 CRTThreadTraits,否则为 Win32ThreadTraits。需要实现的方法有:

    • Execute()
    • 这是当内核对象被信号时将被执行的线程回调函数。

    • CloseHandle()
    • 此方法可用于调用任何关联的内核对象。除了接口方法之外,我们还添加了两个额外的辅助方法,称为:

    • ResumeThread()
    • 此方法通过信号内核对象来启动线程。

      ::SetEvent(m_hEvent);
    • FreezeThread()
    • 此方法通过重置内核对象来挂起线程。

      ::ResetEvent(m_hEvent);

      此外,构造函数和析构函数

    • CSerializer()
    • 在这里的构造函数中,我们初始化 CWorkerThread 成员变量。为此,我们必须调用 CWorkerThreadInitialize() 方法。然后创建一个内核对象并调用 AddHandle() 方法将其添加到成员变量中。

      m_thread.Initialize(); 
      m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); 
      m_thread.AddHandle(m_hEvent, this, NULL);
    • ~CSerializer()
    • 在这里的析构函数中,我们通过调用 CWorkerThreadRemoveHandle()Shutdown() 方法来反初始化 CWorkerThread 成员变量。

      m_thread.RemoveHandle(m_hEvent); 
      m_thread.Shutdown();
  3. IWorkerThreadClient 实现类 CSerializer 的构造函数中,调用 CWorkerThreadInitialize()AddHandle() 方法。
  4. 然后在 CLogManager 客户端类中,创建一个 IWorkerThreadClient 实现类 CSerializer 的实例,并调用 ResumeThread() 方法来启动线程。
  5. m_CSerializer.ResumeThread();
  6. 当需要挂起线程时,调用 m_CSerializer.FreezeThread() 方法。
  7. 最后,要终止线程,请调用 m_CSerializer.Shutdown()。这在 IWorkerThreadClient 实现类 CSerializer 的析构函数中完成。

CSerializer 类

CLogManager 类有一个类型为 CSerializer 的成员变量。CSerializer 实现 IWorkerThreadClient 接口。类中提供的各种方法如上所述。

CLogManager 类

这个 co class 封装了日志记录信息所需的必要方法。应用程序可以使用此 co class 实例进行日志记录。此 co class 具有以下方法:

  • Initialize()
  • 通过清理和设置日志文件夹以及其他必要的初始化来初始化 LogManager co class。

  • Log()
  • 使用此方法记录任何信息。它接收一个类型为 ILogData* 的参数。这是一个 co class,包含有关特定日志的必要信息。ILogData 的属性包括 LogMessageMethodnameModulenameSevirityThreadname 以及数据存储的 Destination

如何使用 CLogmanager 类

co 创建 CLogManager 类的实例,然后通过传递 IConfig co class 的对象来调用 CLogManager 类的 Initialize 方法。这将初始化日志目录。

LogManagerPtr.CreateInstance(  __uuidof(LogManLib::LogManager));

LogManLib::IConfigPtr ConfigPtr(__uuidof(LogManLib::Config));
ConfigPtr->AppName = _bstr_t("CWorkerThread Demo App");
ConfigPtr->DbConnectString = _bstr_t("App db connect string if you have");
ConfigPtr->DbName = _bstr_t("App db name if you have");
ConfigPtr->DbServer = _bstr_t("App db server here if you have");

TCHAR Buffer[MAX_PATH];
DWORD dwRet = GetCurrentDirectory(MAX_PATH, Buffer);
ConfigPtr->LogDir = _bstr_t(Buffer);

LogManagerPtr->Initialize(ConfigPtr);

然后调用 log 方法将数据记录到指定的日志目录。

CString strMessage = _T("");
this->GetDlgItemTextW(IDC_EDIT1,strMessage);
LogManLib::ILogDataPtr pILogDataPtr(>(__uuidof(LogManLib::LogData));
pILogDataPtr->Message = _bstr_t(strMessage);
pILogDataPtr->MethodName = _bstr_t(_T("OnBnClickedLog"));
pILogDataPtr->ModuleName = _bstr_t(_T("CCWorkerThreadDemoDlg"));
pILogDataPtr->Sevirity = 0;
pILogDataPtr->ThreadName = _bstr_t(_T("THREAD_NAME"));
LogManagerPtr->Log(pILogDataPtr);

修订历史

  • 2006 年 7 月 28 日 - 版本 1.0 - 首次发布。
© . All rights reserved.