Worker Threads

Thread shutdown without polling

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).

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“There are only 3 numbers of interest to a computer scientist: 1, 0 and infinity”