#ifndef __WorkerThread_h__
#define __WorkerThread_h__
/////////////////////////////////////////////////////////////////////////////
// WorkerThread.h | Declaration of the TCWorkerThread class.
//
#include <list>
#include <queue>
#include "TCThread.h"
#include "AutoHandle.h"
#include "AutoCriticalSection.h"
#include "ObjectLock.h"
/////////////////////////////////////////////////////////////////////////////
// Type definition for a user-defined callback function that is called by the
// implementation of TCWorkerThread. The callback function is expected to
// release any resources associated with the specified thread arguments.
//
// A class derived from TCWorkerThread should override the virtual
// TCWorkerThread::OnGetWorkItemRelProc method and return a function pointer
// of this type. Typically, the class will specify a static class method.
//
// See Also: TCWorkerThread, TCWorkerThread::OnGetWorkItemRelProc,
// TCWorkerThread::XWorkItem, TCWorkerThread_ArgumentReleaseProc
typedef void (WINAPI *TC_WorkItemRelProc)(UINT, int, LPARAM*);
#ifdef _DOCJET_ONLY
///////////////////////////////////////////////////////////////////////////
// The TCWorkerThread_ArgumentReleaseProc function is an
// application-defined callback function used with the TCWorkerThread
// class. It receives notification that a queued element has finished
// executing in the worker thread. The callback function should release
// any resources associated with the specified arguments. The
// TC_WorkItemRelProc type defines a pointer to this callback function.
// TCWorkerThread_ArgumentReleaseProc is a placeholder for the
// application-defined function name.
//
// Parameters:
// idMsg - The message identifier used to identify different elements of
// work that the TCWorkerThread-derived class has posted to the worker
// thread. Note that this is *not* a window message and can be any UINT
// value meaningful to the derived class. Usually, the derived class will
// declare an enumeration in the class to define these identifiers.
// cParams - The number of LPARAM arguments pointed to by the
// /rgParams/ parameter.
// rgParams - An array of LPARAM arguments specified when the element of
// work was queued to the worker thread.
//
// See Also: TC_WorkItemRelProc, TCWorkerThread,
// TCWorkerThread::OnGetWorkItemRelProc, TCWorkerThread::PostMessage,
// TCWorkerThread::PostMessageV, TCWorkerThread::PostMessageEx,
// TCWorkerThread::XWorkItem
void WINAPI TCWorkerThread_ArgumentReleaseProc(UINT idMsg, int cParams,
LPARAM* rgParams);
#endif // _DOCJET_ONLY
/////////////////////////////////////////////////////////////////////////////
// TCWorkerThread provides a base class from which a class can inherit to
// perform elements of work within a seperate worker thread.
//
// Possible uses for this class include:
// + Performing delayed garbage collection.
// + Performing less time-critical tasks outside of a main thread.
//
// To use this class, first include it as a public base in your
// class declaration:
//
// class CMyClass : public TCWorkerThread
// {
//
// It should rarely conflict with other base class method names, so it is
// also suitable for use with multiple inheritance:
//
// class CMIClass :
// public IUnknown,
// public TCWorkerThread
// {
//
// Next, declare the overrides for the pure virtual methods of the class:
//
// // Overrides
// protected:
// virtual IUnknown* OnGetUnknown();
// virtual TC_WorkItemRelProc OnGetWorkItemRelProc();
// virtual void OnMessage(UINT idMsg, int cParams, LPARAM* rgParams);
//
// Following that, you should declare a *static* method used to release
// message arguments:
//
// // Implementation
// protected:
// static void WINAPI ArgumentReleaseProc(UINT idMsg, int cParams,
// LPARAM* rgParams);
//
// Then, declare an unnamed enumeration in your class, naming the type (or
// types) of work that your class needs to perform from the worker thread:
//
// // Types
// protected:
// enum {e_NotifyEngineer, e_NotifyManager, e_NotifyExecutive};
// };
//
// Next, implement OnGetUnknown. If your class is not a COM object, this can
// simply return NULL:
//
// IUnknown* CMyClass::OnGetUnknown()
// {
// return NULL;
// }
//
// But, if your class *does* implement a component object, you must return
// an IUnknown pointer on your class. Do *not* AddRef the interface pointer:
//
// IUnknown* CMIClass::OnGetUnknown()
// {
// // ATL objects should use GetUnknown() instead of 'this'
// return static_cast<IUnknown*>(this);
// }
//
// The implementation of OnGetWorkItemRelProc is just as easy:
//
// TC_WorkItemRelProc CMyClass::OnGetWorkItemRelProc()
// {
// return ArgumentReleaseProc;
// }
//
// Note: The OnGetWorkItemRelProc can return NULL only if *all* the arguments
// passed to the PostMessage, PostMessageV, or PostMessageEx methods do not
// represent open resources. Open resources include things such as pointers,
// handles, registry keys, etc. This is rarely the case, but if such
// conditions do exists for your class, OnGetWorkItemRelProc can return NULL
// and the ArgumentReleaseProc static callback would not need to be declared
// or implemented.
//
// The last of the overrides is where you actually perform the work for a
// queued element. Usually, however, if multiple types of work are defined,
// the method will just switch, unpack the arguments, and call a function to
// peform one specific type of work:
//
// void CMyClass::OnMessage(UINT idMsg, int cParams, LPARAM* rgParams)
// {
// switch (idMsg)
// {
// case e_NotifyEngineer:
// {
// assert(2 == cParams);
// CBonus* psi = reinterpret_cast<CBonus*>(rgParams[0]);
// LPCTSTR psz = reinterpret_cast<LPCTSTR>(rgParams[1]);
// NotifyEngineer(psi, psz);
// return;
// }
// case e_NotifyManager:
// {
// assert(1 == cParams);
// CWeatherRpt* pwr = reinterpret_cast<CWeatherRpt*>(rgParams[0]);
// NotifyManager(pwr);
// return;
// }
// case e_NotifyExecutive:
// {
// assert(1 == cParams);
// CBottomLine* pbl = reinterpret_cast<CBottomLine*>(rgParams[0]);
// NotifyExecutive(pbl);
// return;
// }
// }
// }
//
// Note: If you *really* dislike switch statements, you could specify a
// class function pointer as the /idMsg/ parameter to the PostMessage
// methods. Then, the switch statement (and the enum) could be eliminated.
// Notice, though, that this approach would push the parameter unpacking down
// to the individual member functions, which may also be considered more
// elegant.
//
// void CMyClass::OnMessage(UINT idMsg, int cParams, LPARAM* rgParams)
// {
// // Declare a typedef for a member function pointer
// typedef void (CMyClass::*WORK_PROC)(int, LPARAM*);
// …
// // Cast the idMsg back to a WORK_PROC
// WORK_PROC pfn = (WORK_PROC)idMsg;
// …
// // Call the member function
// (this->*pfn)(cParams, rgParams);
// }
//
// Finally, the static method needs to be implemented to release the memory
// used by the parameters:
//
// void WINAPI CMyClass::ArgumentReleaseProc(UINT idMsg, int cParams,
// LPARAM* rgParams)
// {
// switch (idMsg)
// {
// case e_NotifyEngineer:
// {
// assert(2 == cParams);
// CBonus* psi = reinterpret_cast<CBonus*>(rgParams[0]);
// LPCTSTR psz = reinterpret_cast<LPCTSTR>(rgParams[1]);
// delete psi;
// delete [] psz;
// return;
// }
// case e_NotifyManager:
// {
// assert(1 == cParams);
// CWeatherRpt* pwr = reinterpret_cast<CWeatherRpt*>(rgParams[0]);
// delete pwr;
// return;
// }
// case e_NotifyExecutive:
// {
// assert(1 == cParams);
// CBottomLine* pbl = reinterpret_cast<CBottomLine*>(rgParams[0]);
// delete pbl;
// return;
// }
// }
// }
//
// You probably noticed that this method does most of the same things that
// the virtual OnMessage method has to perform. It would seem likely that the
// two operations (process and release) on the work element could be done in
// the same method. A flag would indicate which operation to be performed on
// the arguments once they're unpacked. This /could/ be done, but with one
// minor caveat. Since the release method is static, the common function
// would have to take great care to not reference any instance data or call
// any non-static method. If this sounds like a reasonable tradeoff, the
// static release method could be coded as follows:
//
// void WINAPI CMyClass::ArgumentReleaseProc(UINT idMsg, int cParams,
// LPARAM* rgParams)
// {
// // Declare a typedef for a member function pointer
// typedef void CMyClass::*WORK_PROC(int, LPARAM*);
// …
// // Cast the idMsg back to a WORK_PROC
// WORK_PROC* pfn = (WORK_PROC*)idMsg;
// …
// // Create a NULL instance pointer (be careful with it!)
// CMyClass* pThis = (CMyClass*)(NULL);
// …
// // Call the member function
// (pThis->*pfn)(cParams, rgParams);
// }
//
// For this to work correctly, the member function must *not* be virtual, and
// it needs to check the *this* pointer to determine if it is to release the
// arguments or to process them:
//
// void CMyClass::NotifyExecutive(int cParams, LPARAM* rgParams)
// {
// // Unpack the arguments
// assert(1 == cParams);
// CBottomLine* pbl = reinterpret_cast<CBottomLine*>(rgParams[0]);
// …
// // Delete or process the arguments
// if (this)
// {
// // Notify the executive of the bottom line...
// }
// else
// {
// delete pbl;
// }
// }
//
// Note: For a short discussion of the reason that the release method must be
// static, refer to the documentation for the OnGetWorkItemRelProc override.
//
// Finally, in the line of normal processing, when the derived object needs
// to queue the element of work to the worker thread, a simple call to one
// of the PostMessage methods is all that it takes:
//
// HRESULT CMyClass::ChangeBottomLine(double dRevenue, double dExpenses)
// {
// // Save the values, etc...
// // ...
// …
// // Notify the executives of the change
// CBottomLine* pbl = new CBottomLine(dRevenue, dExpenses);
// PostMessage(e_NotifyExecutive, pbl);
// …
// // Indicate success
// return S_OK;
// }
//
// Of course, the PostMessage call would look slightly different if the
// non-switch approach were being used:
//
// PostMessage(UINT(NotifyExecutive), pbl);
//
// ToDo: The majority of this class's functionality would be useful outside
// of the singleton scenario. The argument handling and thread processing
// should be moved into either a base class or another stand-alone class.
//
// See Also: TCWorkerThread_ArgumentReleaseProc,
// TCWorkerThread::XWorkItem
class TCWorkerThread
{
// Group=Types
protected:
class XWorkItem;
// Construction / Destruction
public:
TCWorkerThread();
virtual ~TCWorkerThread();
// Disallow copy constructor
private:
TCWorkerThread(const TCWorkerThread&);
// Attributes
public:
bool IsCurrentThread();
// Operations
public:
void Close();
void PostMessage(UINT idMsg, int cParams, ...);
void PostMessageV(UINT idMsg, int cParams, va_list argptr);
void PostMessageEx(UINT idMsg, int cParams, LPARAM* rgParams);
// Overrides
protected:
virtual IUnknown* OnGetUnknown() = 0;
virtual TC_WorkItemRelProc OnGetWorkItemRelProc() = 0;
virtual void OnMessage(UINT idMsg, int cParams, LPARAM* rgParams) = 0;
// Implementation
protected:
static unsigned WINAPI ThreadThunk(void*);
void ThreadProc();
void DispatchWorkItem(XWorkItem* pArgs);
void DestroyWorkItem(XWorkItem* pArgs);
// Group=Types
protected:
typedef std::queue<XWorkItem*, std::list<XWorkItem*> > XQueue;
typedef TCObjectLock<TCAutoCriticalSection> XLockQueue;
// Group=Data Members
protected:
#pragma pack(push, 4)
/////////////////////////////////////////////////////////////////////////
// Description: Pointer to the thread object.
//
// See Also: TCThread
TCThread* m_pth;
/////////////////////////////////////////////////////////////////////////
// Description: A flag indicating that the Close method has been called.
//
// A flag indicating that the Close method has been called. This is used
// by the Close method to ensure that the reference count of the worker
// thread does not get decremented more than once by an instance. This is
// important since the destructor calls Close.
//
// See Also: TCWorkerThread::destructor, TCWorkerThread::Close
BOOL m_bClosed;
#pragma pack(pop)
///////////////////////////////////////////////////////////////////////////
// Description: An event handle used to signal when new work items are
// available on the queue.
//
// See Also: TCHandle, TCAutoHandle
TCHandle m_shevtQueueNotEmpty;
///////////////////////////////////////////////////////////////////////////
// Description: An event handle used to signal when the thread should be
// shutdown.
//
// See Also: TCHandle, TCAutoHandle
TCHandle m_shevtShutdown;
///////////////////////////////////////////////////////////////////////////
// Description: Controls synchronized access to the Queue collection.
//
// See Also: TCAutoCriticalSection
TCAutoCriticalSection m_csQueue;
///////////////////////////////////////////////////////////////////////////
// Description: The queue of work items.
//
// See Also: TCWorkerThread::XQueue
XQueue m_queue;
};
/////////////////////////////////////////////////////////////////////////////
// Group=Attributes
inline bool TCWorkerThread::IsCurrentThread()
{
return !m_pth || GetCurrentThreadId() == m_pth->m_nThreadID;
}
/////////////////////////////////////////////////////////////////////////////
#endif // !__WorkerThread_h__