The technique I use that polls every few seconds does have two implications: it makes the thread active every few seconds, and it sets a limit on responsiveness on a shutdown. The shutdown of the application becomes limited by the maximum time it takes to get out of the thread-wait operation. There is an alternative implementation I have also used, which involves using a second Event.
HANDLE ShutdownEvent;
This should be initialized via CreateEvent. What I do when I'm using this technique is include it in a class derived from CWinThread, which makes the thread creation slightly trickier. This is because AfxBeginThread always creates a new CWinThread object, but if you need your own CWinThread-derived class, you can't use AfxBeginThread. The technique shown below generalizes this. Note that if I wanted to be really general, I would create a template class. I leave that as an Exercise For The Reader.
/*********************************************************************** * class CMyThread ***********************************************************************/ class CMyThread : public CWinThread { public: CMyThread( ); virtual ~CMyThread( ); static CMyThread * BeginThread(LPVOID p); void Shutdown( ); enum { Error, Running, Shutdown, Timeout }; protected: // data HANDLE ShutdownEvent; HANDLE PauseEvent; }; /********************************************************************** * CMyThread::CMyThread * Inputs: * AFX_THREADPROC proc: Function to be called * LPVOID p: Parameter passed to proc ***********************************************************************/ CMyThread::CMyThread(AFX_THREADPROC proc, LPVOID p ) : CWinThread(proc, p) { m_bAutoDelete = FALSE; ShutdownEvent = ::CreateEvent(NULL, // security TRUE, // manual-reset FALSE, // not signaled NULL); // anonymous PauseEvent = ::CreateEvent(NULL, // security TRUE, // manual-reset TRUE, // signaled NULL); // anonymouse } /********************************************************************** * CMyThread::~CMyThread **********************************************************************/ CMyThread::~CMyThread( ) { ::CloseHandle(ShutDownEvent); ::CloseHandle(PauseEvent); } /********************************************************************* * CMyThread::BeginThread * Result: CMyThread * * Newly-created CMyThread object *********************************************************************/ CMyThread * CMyThread::BeginThread(AFX_THREADPROC proc, LPVOID p) { CMyThread * thread = new CMyThread(proc, p); if(!thread->CreateThread( )) { /* failed */ delete thread; return NULL; } /* failed */ return thread; } /********************************************************************* * CMyThread::Wait * Result: DWORD * WAIT_OBJECT_0 if shutting down * WAIT_OBJECT_0+1 if not paused * Notes: * The shutdown *must* be the 0th element, since the normal * return from an unpaused event will be the lowest value OTHER * than the shutdown index *********************************************************************/ DWORD CMyThread::Wait( ) { HANDLE objects[2]; objects[0] = ShutdownEvent; objects[1] = PauseEvent; DWORD result = ::WaitForMultipleObjects(2, objects, FALSE, INFINITE); switch(result) { /* result */ case WAIT_TIMEOUT: return Timeout; case WAIT_OBJECT_0: return Shutdown; case WAIT_OBJECT_0 + 1: return Running; default: ASSERT(FALSE); // unknown error return Error; } /* result */ } /******************************************************************** * CMyThread::Shutdown * Effect: * Sets the shutdown event, then waits for the thread to shut * down ********************************************************************/ void CMyThread::Shutdown( ) { SetEvent(ShutdownEvent); ::WaitForSingleObject(m_hThread, INFINITE); }
Note that I don't make provision here for the full set of options for CreateThread, since the threads I create do not need flags, stack size, or security attributes; you would need to make the obvious extensions if you need these features.
To call it from an application, I do something like the following. In the declaration of the class in which the thread will run, such as a view class, I add declarations like
CMyThread * thread; // worker thread static UINT MyComputation(LPVOID me); void ComputeLikeCrazy( );
Then I add methods, such as this one that responds to a menu item or pushbutton in a view:
void CMyView::OnComputationRequest( ) { thread = CMyThread::BeginThread(MyComputation, this); } UINT CMyView::MyComputation(LPVOID me) // static method! { CMyView * self = (CMyView *)me; self->ComputeLikeCrazy( ); }
The code below then shows how I implement a "pause" capability. Alternatively, the PauseEvent variable could represent a Semaphore on a queue or some other synchronization mechanism. Note, however, that it is more complex if you want to wait for a semaphore, a shutdown, or a pause. In this case, because you can only wait on "or" or "and" of the events, and not more complex relationships, you will probably need to nest two WaitForMultipleObjects, one for the semaphore-or-shutdown combination and one for the pause-or-shutdown combination. Although I don't show it below, you can additionally combine this technique with a timeout. Note that in the example below, the running flag is actually local, rather than being a class member variable, and is implicitly handled by the case decoding the ShutdownEvent.
void CMyView::ComputeLikeCrazy( ) { BOOL running = TRUE; while(running) { /* loop */ DWORD result = thread->Wait( ); switch(result) { /* result */ case CMyThread::Timeout: // if you want a timeout... continue; case CMyThread::Shutdown: // shutdown event running = FALSE; continue; case CMyThread::Running: // unpaused break; } /* result */ // ... // ... compute one step here // ... } /* loop */ }
Note that I make provision for a timeout case, even though the current implementation does not provide for it (an Exercise For The Reader).
Comments