Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / ATL

CWorkerThread and IWorkerThreadClient – Looking Further

4.67/5 (8 votes)
28 Jul 2006Ms-PL5 min read 1   518  
A tutorial on how to use the ATL7 thread class CWorkerThread and its associated helper classes CRTThreadTraits, IWorkerThreadClient etc. It also presents a generic logging component which can be used in CPU intensive applications.

Sample image

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.

  1. Here, first we have to implement the IWorkerThreadClient interface and create a CWorkerThread member variable in the implementation class.
  2. 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.)

  3. Then in the client class, create an instance of the IWorkerThreadClient implementation class and call the ResumeThread() method to start the thread.
  4. 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.
  5. 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.

  1. As mentioned above, first we have to implement the IWorkerThreadClient interface.
  2. In the sample project, CSerializer is the IWorkerThreadClient implementation class. Then, create a CWorkerThread member variable inside the IWorkerThreadClient derived CSerializer class.

    C++
    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.

      C++
      ::SetEvent(m_hEvent);
    • FreezeThread()
    • This method suspends the thread by resetting the kernel object.

      C++
      ::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.

      C++
      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.

      C++
      m_thread.RemoveHandle(m_hEvent); 
      m_thread.Shutdown();
  3. In the constructor of the IWorkerThreadClient implementation CSerializer class, invoke the Initialize() and AddHandle() methods of CWorkerThread.
  4. Then in the CLogManager client class, create an instance of the IWorkerThreadClient implementation CSerializer class and call the ResumeThread() method to start the thread.
  5. C++
    m_CSerializer.ResumeThread();
  6. Call the m_CSerializer.FreezeThread() method when it is required to suspend the thread.
  7. 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.

C++
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.

C++
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.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)