CWorkerThread 和 IWorkerThreadClient – 深入了解






4.67/5 (8投票s)
本教程介绍如何使用 ATL7 线程类 CWorkerThread 及其相关的辅助类 CRTThreadTraits、IWorkerThreadClient 等。此外,它还介绍了一个可用于 CPU 密集型应用程序的通用日志记录组件。
引言
本文旨在深入理解 ATL7 的 CWorkerThread
类及其用法。我一直在寻找一个可以复制代码并粘贴到我当前项目中的示例。不幸的是,我甚至无法通过 Google 找到一个。这就是本文的诞生原因。此外,这个线程类的使用方式并不那么直接,它确实需要比 MSDN 中提供更多的文档。
这个类的使用方式有点令人困惑。作为 MFC 开发人员,我们都非常清楚 CWinThread
类的用法。同样,在这里我们可能会认为 CWorkerThread
是要在我们的客户端类中使用的顶层主类。但事实并非如此。
包含的示例项目基本上是一个通用的日志记录组件,可用于任何应用程序的日志记录。该组件的实现方式对于处理密集型应用程序并需要在不影响核心处理时间的情况下记录大量数据至关重要。在我的案例中,该应用程序处理数百万条记录,平均完成时间约为 4 天。在处理过程中,每当出现错误时,都需要转储大约半兆字节大小的数据。如果它是同步日志记录,就会影响整体性能。所以我将其设为异步。为此,该组件将内容记录到一个内存队列中。一个单独的线程(CWorkerThread
)轮询该队列并将数据转储到指定的辅助存储中。
CWorkerThread 类如何使用
这个类的使用方式有点令人困惑。作为 MFC 开发人员,我们都非常清楚 CWinThread
类的用法。同样,在这里我们可能会认为 CWorkerThread
是要在我们的客户端类中使用的顶层主类。但事实并非如此。
- 在这里,我们首先必须实现
IWorkerThreadClient
接口,并在实现类中创建一个CWorkerThread
成员变量。 - 然后在
IWorkerThreadClient
实现类的构造函数中 - 首先,调用
CWorkerThread
成员变量的Initialize()
方法。 - 然后,调用
CWorkerThread
成员变量的AddHandle()
方法。AddHandle()
方法有两个参数: - 用于同步的事件句柄。
- 线程回调函数指针(不完全是函数指针,而是
IWorkerThreadClient
派生类指针本身;IWorkerThreadClient
接口有一个名为Execute()
的方法作为回调函数)。 - 然后在客户端类中,创建一个
IWorkerThreadClient
实现类的实例,并调用ResumeThread()
方法来启动线程。 - 调用
ResumeThread()
后,内核对象将被信号,并且IWorkerThreadClient::Execute()
将被调用,直到内核对象进入非信号状态。通过调用IWorkerThreadClient::FreezeThread()
方法可以改变内核对象的状态。 - 最后,要终止线程,请调用
CWorkerThread::Shutdown()
。这可以在IWorkerThreadClient
实现类的析构函数中完成。
(我们也可以使用 Addtimer()
方法而不是 AddHandle()
。但示例项目使用 AddHandle()
方法并信号内核对象来控制回调函数的调用。Addtimer()
会定期调用回调 Execute()
函数,而不是等待内核对象被信号。)
理论部分到此为止。现在是时候动手编码了。正如上面提到的,我在这里使用了一个日志记录组件来实现 CWorkerThread
。该组件有一个名为 CLogManager
的 co class,应用程序可以使用它进行日志记录。
CWorkerThread 在 LogManager 示例项目中的用法
在示例项目中,CLogManager
是客户端类,它使用一个单独的线程来异步地将日志数据序列化到辅助存储。CLogManager
实现为 ATL COM 类,而这个单独的线程使用 CWorkerThread
和 IWorkerThreadClient
实现。
- 如上所述,我们首先必须实现
IWorkerThreadClient
接口。 Execute()
CloseHandle()
ResumeThread()
FreezeThread()
CSerializer()
~CSerializer()
- 在
IWorkerThreadClient
实现类CSerializer
的构造函数中,调用CWorkerThread
的Initialize()
和AddHandle()
方法。 - 然后在
CLogManager
客户端类中,创建一个IWorkerThreadClient
实现类CSerializer
的实例,并调用ResumeThread()
方法来启动线程。 - 当需要挂起线程时,调用
m_CSerializer.FreezeThread()
方法。 - 最后,要终止线程,请调用
m_CSerializer.Shutdown()
。这在IWorkerThreadClient
实现类CSerializer
的析构函数中完成。
在示例项目中,CSerializer
是 IWorkerThreadClient
实现类。然后,在派生于 IWorkerThreadClient
的 CSerializer
类中创建一个 CWorkerThread
成员变量。
CWorkerThread<CRTThreadTraits> m_thread;
如上所示,CWorkerThread
是一个模板类,其参数类型为 CRTThreadTraits
。它可以是 CRTThreadTraits
或 Win32ThreadTraits
。如果线程使用任何 C 运行时函数,则必须是 CRTThreadTraits
,否则为 Win32ThreadTraits
。需要实现的方法有:
这是当内核对象被信号时将被执行的线程回调函数。
此方法可用于调用任何关联的内核对象。除了接口方法之外,我们还添加了两个额外的辅助方法,称为:
此方法通过信号内核对象来启动线程。
::SetEvent(m_hEvent);
此方法通过重置内核对象来挂起线程。
::ResetEvent(m_hEvent);
此外,构造函数和析构函数
在这里的构造函数中,我们初始化 CWorkerThread
成员变量。为此,我们必须调用 CWorkerThread
的 Initialize()
方法。然后创建一个内核对象并调用 AddHandle()
方法将其添加到成员变量中。
m_thread.Initialize();
m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
m_thread.AddHandle(m_hEvent, this, NULL);
在这里的析构函数中,我们通过调用 CWorkerThread
的 RemoveHandle()
和 Shutdown()
方法来反初始化 CWorkerThread
成员变量。
m_thread.RemoveHandle(m_hEvent);
m_thread.Shutdown();
m_CSerializer.ResumeThread();
CSerializer 类
CLogManager
类有一个类型为 CSerializer
的成员变量。CSerializer
实现 IWorkerThreadClient
接口。类中提供的各种方法如上所述。
CLogManager 类
这个 co class 封装了日志记录信息所需的必要方法。应用程序可以使用此 co class 实例进行日志记录。此 co class 具有以下方法:
Initialize()
Log()
通过清理和设置日志文件夹以及其他必要的初始化来初始化 LogManager
co class。
使用此方法记录任何信息。它接收一个类型为 ILogData*
的参数。这是一个 co class,包含有关特定日志的必要信息。ILogData
的属性包括 LogMessage
、Methodname
、Modulename
、Sevirity
、Threadname
以及数据存储的 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 - 首次发布。