#ifndef __WorkerThread_h__ #define __WorkerThread_h__ ///////////////////////////////////////////////////////////////////////////// // WorkerThread.h | Declaration of the TCWorkerThread class. // #include #include #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(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(rgParams[0]); // LPCTSTR psz = reinterpret_cast(rgParams[1]); // NotifyEngineer(psi, psz); // return; // } // case e_NotifyManager: // { // assert(1 == cParams); // CWeatherRpt* pwr = reinterpret_cast(rgParams[0]); // NotifyManager(pwr); // return; // } // case e_NotifyExecutive: // { // assert(1 == cParams); // CBottomLine* pbl = reinterpret_cast(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(rgParams[0]); // LPCTSTR psz = reinterpret_cast(rgParams[1]); // delete psi; // delete [] psz; // return; // } // case e_NotifyManager: // { // assert(1 == cParams); // CWeatherRpt* pwr = reinterpret_cast(rgParams[0]); // delete pwr; // return; // } // case e_NotifyExecutive: // { // assert(1 == cParams); // CBottomLine* pbl = reinterpret_cast(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(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 > XQueue; typedef TCObjectLock 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__