Introduction
This article intends to give an in depth understanding of the ATL7 CWorkerThread
class and its use. I had been looking for a sample code for copy and paste in my current project. Unfortunately, I couldn't find even get one using Google. That's how this article took birth. Also, it is not that straightforward the way this thread class has to be used, and it really deserves more documentation than what is available in MSDN.
The way the class has to be used is a bit confusing. As an MFC developer, we all are well aware of the CWinThread
class usage. Similarly, here we may think that CWorkerThread
is the top level main class to be used in our client class. But it is not.
The included sample project is basically a generic logging component which can be used for logging purposes in any application. The way the component implemented is very crucial if your application is process intensive and need to log a bulk amount of data without affecting the core processing time. In my case, the application was dealing with millions of records and the average completion time was around 4 days. During the processing, it needs to dump data of around half an MB in size whenever an error comes. If it is synchronized logging, it would have affected the overall performance. So I made it async. For that, the component logs the contents into an in memory queue. A separate thread (CWorkerThread
) polls this queue and dumps the data into a secondary storage specified.
How CWorkerThread class has to be used
The way the class has to be used is a bit confusing. As an MFC developer, we all are well-aware how the CWinThread
class has to be used. Similarly, here we may think that CWorkerThread
is the top level main class to be used in our client class. But it is not.
- Here, first we have to implement the
IWorkerThreadClient
interface and create a CWorkerThread
member variable in the implementation class. - Then in the constructor of the
IWorkerThreadClient
implementation class:
- First, call the
Initialize()
method of the CWorkerThread
member variable. - Then, call the
AddHandle()
method of the CWorkerThread
member variable. The AddHandle()
method has two parameters:
- An event handle for synchronization.
- The thread callback function pointer (not exactly a function pointer, but the
IWorkerThreadClient
derived class pointer itself; the IWorkerThreadClient
interface has a method called Execute ()
as the callback function).
(Instead of AddHandle()
, we can use the Addtimer()
method as well. But the sample project uses the AddHandle()
approach and signals the kernel object for controlling the callback function invocation. Addtimer()
periodically invokes the callback Execute()
function instead of waiting for the kernel object to be signaled.)
- Then in the client class, create an instance of the
IWorkerThreadClient
implementation class and call the ResumeThread()
method to start the thread. - Once
ResumeThread()
is called, the kernel object will be signaled and IWorkerThreadClient::Execute()
will get invoked, until the kernel object gets into a non signaled state. The kernel object state can be changed by calling the IWorkerThreadClient::FreezeThread()
method. - Finally, to terminate the thread, call
CWorkerThread::Shutdown()
. This can be done in the destructor of the IWorkerThreadClient
implementation class.
It's enough for theory part. Now its time to wet our hand with some coding. Here I used a Logging component to implement the CWorkerThread
as mentioned above. The component has a co class called CLogManager
which can be used by applications for logging.
How CWorkerThread is used in the sample project LogManager
In the sample project, CLogManager
is the client class which uses a separate thread for serializing the logged data to a secondary storage asynchronously. CLogManager
is implemented as an ATL COM class, and this separate thread is implemented using CWorkerThread
and IWorkerThreadClient
.
- As mentioned above, first we have to implement the
IWorkerThreadClient
interface. In the sample project, CSerializer
is the IWorkerThreadClient
implementation class. Then, create a CWorkerThread
member variable inside the IWorkerThreadClient
derived CSerializer
class.
CWorkerThread<CRTThreadTraits> m_thread;
As shown above, CWorkerThread
is a template class with a parameter type of CRTThreadTraits
. This can be either CRTThreadTraits
or Win32ThreadTraits
. If the thread uses any C runtime functions, then this has to be CRTThreadTraits
else Win32ThreadTraits
. The methods to be implemented are:
Execute()
This is the thread callback function which will get executed when the kernel object is signaled.
CloseHandle()
This method can be used to call any associated kernel objects. In addition to the interface methods, we have added two additional helper methods called:
ResumeThread()
This method starts the thread by signaling the kernel object.
::SetEvent(m_hEvent);
FreezeThread()
This method suspends the thread by resetting the kernel object.
::ResetEvent(m_hEvent);
Also, the constructor and destructor too:
CSerializer()
Here in the constructor, we initialize the CWorkerThread
member variable. For this, we have to call the Initialize()
method of CWorkerThread
. Then create a kernel object and add to the member variable by calling the AddHandle()
method.
m_thread.Initialize();
m_hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
m_thread.AddHandle(m_hEvent, this, NULL);
~CSerializer()
Here in the destructor, we de-initialize the CWorkerThread
member variable by calling the RemoveHandle()
and Shutdown()
CWorkerThread
methods.
m_thread.RemoveHandle(m_hEvent);
m_thread.Shutdown();
- In the constructor of the
IWorkerThreadClient
implementation CSerializer
class, invoke the Initialize()
and AddHandle()
methods of CWorkerThread
. - Then in the
CLogManager
client class, create an instance of the IWorkerThreadClient
implementation CSerializer
class and call the ResumeThread()
method to start the thread.
m_CSerializer.ResumeThread();
- Call the
m_CSerializer.FreezeThread()
method when it is required to suspend the thread. - Finally, to terminate the thread, call
m_CSerializer.Shutdown()
. This is done in the destructor of the IWorkerThreadClient
implementation CSerializer
class.
CSerializer class
The CLogManager
class has a member variable of type CSerializer
. CSerializer
implements the IWorkerThreadClient
interface. The various methods available in the classes are as explained above.
CLogManager class
This coclass wraps the necessary methods for logging the information. An application can use this coclass instance for logging. This coclass has methods:
Initialize()
Initializes the LogManager
co class by cleaning and setting up the log folder and other necessary initializations.
Log()
Use this method to log any information. It receives a parameter of type ILogData*
. This is a co class with the necessary information pertaining to a particular log. Properties of ILogData
include LogMessage
, Methodname
, Modulename
, Sevirity
, Threadname
, and the Destination
where the data is stored.
How to use the CLogmanager class
Co create an instance of the CLogManager
class, then call the Initialize
method of the CLogManager
class by passing an object of the IConfig
co class. This will initialize the log directory.
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);
Then call the log method to log data to the log directory mentioned.
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);
Revision history
- July 28, 2006 - Version 1.0 - First release.