Skip navigation links
IT services and product development
Menu
TwoLogs
IT services and product development

Source code for C_Thread

Download

The source files for this module are listed below.  You can also download the module C_Thread as a zip archive; this archive contains all the source files and documentation.

Description

CThread provides easy multi-threading support to your application, where each thread is maintained by a separate object.

Information

To start using multithreading, derive your class from CThread.  Then overload the Action routine; all processing that has to be done in your worker thread should be placed in this routine.  You can return the result of your Action as a void*, so passing everything up to 4-byte sized return values (the size of a void*) is effortless.  When you however want to return a pointer to something, make sure that someone will deallocate the structure referenced to.  The result of the action can be called from each thread via the Result method.

If you need to do some intitialization or destruction in your thread, overload the ThreadInit and/or the ThreadEnd routines.  These will be guaranteed to run before and after your Action code.

To actually use the threading class, create an object of your CThread derived class and call it's Start method.  The Start method will return immediately, and soon after the thread object's Action routine will start running on a separate thread.

Since the thread object belongs in two threads (the thread controlling part and the worker part), the object may not destruct the originating thread e.g. when it goes out of scope, since the thread object will be needed in the worker as well.

The option of where to destroy the thread object can be specified as a parameter to the Start method.  If you choose to let the object self destruct, the object will be deleted as a last step in the thread's lifetime (after the ThreadEnd method has run).

Three indicators are available to get the current state of the thread.

  • The Start method returns whether the thread could be started at all.
  • The method State will tell you what state the thread is in at the current moment.  Since there is a little startup delay before the Action method kicks in, you should check the State to make sure the Action routine is running at all.  The same goes for the shut-down delay.
  • The IsBusy method will tell you when the thread is busy (starting up, running or finishing).

When the thread is done running it's worker specific code, the State will reflect this.  You can then re-start the thread if you didn't specify that the thread should auto-destruct.

To get thread-specific details about your newly created worker thread object, you can use the GetID and GetHandle methods.  The handle that GetHandle returns will be closed when the thread object destructs.

Notes / todo's

  • To use common resources with multiple threads, make sure you have some access locking for it.  Use our CLock and related classes to do just this.
  • Make sure you have multi-threading enabled in your project settings and/or link with the multi-threaded libraries, if applicable!

Files

Each file belonging to this source code module is listed below.

Thread.h

/*******************************************************************************

  Version: 5
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  This code is freely distributable, as long as this comment remains intact.
  If you find this source useful, you may use this code in your own projects
  free of charge, but some acknowledgement to the author of this code is always
  appreciated :)
  The source is however distributed 'as is' without waranty and/or support, and
  may not be fit for each and every application.  Use it at your own discretion
  and at your own risk.
  The source already has undergone testing.  This doesn't mean however that all
  bugs are removed from this piece of code.  If you find one of them, please
  contact me about it.  I can however not guarantee when and if the bug will be
  fixed.

  More information about this module can be found in the accompanying HTML file.

*******************************************************************************/

#ifndef INCLUDE_TWOLOGS_COMMON_THREAD_H
#define INCLUDE_TWOLOGS_COMMON_THREAD_H

#include <windows.h>

// Phase of the thread execution
enum class EThreadState {
  eIdle,
  eStarting,
  eBusy,
  eFinishing,
  eError
};

class CThread {
public:
  // Con- & destructor
  CThread();
  virtual ~CThread();

  // Starts up the thread.  If you specify the object should auto-
  // destruct, it will be deleted at the end of the thread's work
  bool Start(bool bAutoDestruct);

  // Returns the state the thread is in
  EThreadState State() const;

  // Returns whether the thread is still busy
  bool IsBusy() const;

  // Returns the result
  void* Result() const;

  // Gets the thread's ID
  unsigned int GetID() const;

  // Gets the thread's handle
  HANDLE GetHandle() const;

  // Kills the thread
  void Kill(DWORD nExitCode);

  // Waits for the thread to stop, possibly only waiting the given time (in mSec)
  // Returns if the thread stopped
  bool WaitTillDone(DWORD nWaitTime = INFINITE);

protected:
  // Handle to the thread
  HANDLE m_hThread;

  // The thread's ID
  unsigned int m_nThreadID;

  // The result of the thread
  void* m_puResult;

  // Thread initialization and destruction
  virtual bool ThreadInit();
  virtual void ThreadEnd();

  // Action to perform
  virtual void* Action();

private:
  // Our current state
  EThreadState m_eState;

  // Whether we should auto-destruct
  bool m_bAutoDestruct;

  // Thread startpoint helper function
  static unsigned int __stdcall StartThread(void* puVoidThread);
};

#endif // INCLUDE_TWOLOGS_COMMON_THREAD_H

Thread.cpp

/*******************************************************************************

  Version: 5
  Author:  Carl Colijn, TwoLogs
  Contact: c.colijn@twologs.com
  Source:  https://www.twologs.com/sourcecode

  This code is freely distributable, as long as this comment remains intact.
  If you find this source useful, you may use this code in your own projects
  free of charge, but some acknowledgement to the author of this code is always
  appreciated :)
  The source is however distributed 'as is' without waranty and/or support, and
  may not be fit for each and every application.  Use it at your own discretion
  and at your own risk.
  The source already has undergone testing.  This doesn't mean however that all
  bugs are removed from this piece of code.  If you find one of them, please
  contact me about it.  I can however not guarantee when and if the bug will be
  fixed.

  More information about this module can be found in the accompanying HTML file.

*******************************************************************************/

#include "Thread.h"
#include <process.h>

// Constructor
CThread::CThread():
 m_hThread(NULL),
 m_nThreadID(0),
 m_puResult(nullptr),
 m_eState(EThreadState::eIdle),
 m_bAutoDestruct(false) {
}

// Destructor
CThread::~CThread() {
  // Look if we still have a thread handle lying around
  if (m_hThread != NULL) {
    // Yes -> close the handle
    CloseHandle(m_hThread);
  }
}

// Starts up the thread.  If you specify the object should auto-
// destruct, it will be deleted at the end of the thread's work
bool CThread::Start(bool bAutoDestruct) {
  // Look if we have already started
  bool bSuccess = false;
  if (!IsBusy()) {
    // No -> close any old thread handle
    if (m_hThread != NULL) {
      CloseHandle(m_hThread);
    }

    // And try to start the new thread
    m_bAutoDestruct = bAutoDestruct;
    m_eState = EThreadState::eStarting;
    m_hThread = (HANDLE)_beginthreadex(nullptr, 0, StartThread, this, 0, &m_nThreadID);
    bSuccess = m_hThread != NULL;
    if (!bSuccess) {
      m_eState = EThreadState::eError;
    }
  }
  return bSuccess;
}

// Returns the state the thread is in
EThreadState CThread::State() const {
  return m_eState;
}

// Returns whether the thread is still busy
bool CThread::IsBusy() const {
  return m_eState != EThreadState::eIdle &&
         m_eState != EThreadState::eError;
}

// Returns the result
void* CThread::Result() const {
  return m_puResult;
}

// Gets the thread's ID
unsigned int CThread::GetID() const {
  return m_nThreadID;
}

// Gets the thread's handle
HANDLE CThread::GetHandle() const {
  return m_hThread;
}

// Kills the thread
void CThread::Kill(DWORD nExitCode) {
  // Look if the thread is actually busy
  if (IsBusy()) {
    // Yes -> terminate it forcefully
    if (m_hThread != NULL) {
      TerminateThread(m_hThread, nExitCode);
        // Note: must use _endthreadex according to docu, but can't
        // pass the thread handle to it?!  How lame is that...
    }

    // And correctly set our state
    m_eState = EThreadState::eError;
  }
}

// Waits for the thread to stop, possibly only waiting the given time (in mSec)
// Returns if the thread stopped
bool CThread::WaitTillDone(DWORD nWaitTime) {
  // Look if it has already stopped
  bool bThreadStopped = !IsBusy();
  if (!bThreadStopped) {
    // No -> wait for it to stop
    if (m_hThread != NULL) {
      WaitForSingleObject(m_hThread, nWaitTime);
    }

    // And look if it has stopped now
    bThreadStopped = !IsBusy();
  }

  // And return the verdict
  return bThreadStopped;
}

// Thread initialization
bool CThread::ThreadInit() {
  return true;
}

// Thread destruction
void CThread::ThreadEnd() {
}

// Action to perform
void* CThread::Action() {
  return nullptr;
}

// Thread startpoint helper function
unsigned int __stdcall CThread::StartThread(void* puVoidThread) {
// Easy try-run-and-catch-on-error for simple statements
#define TRYTOPERFORM(__what, __nErrorID) \
  try { \
    __what; \
  } \
  catch (...) { \
    nError = __nErrorID; \
  }

  // Get the thread object to use
  CThread* puThread = (CThread*)puVoidThread;
  DWORD nError = 0;

  // Do thread initialization
  bool bProceedAfterInit = false;
  TRYTOPERFORM(bProceedAfterInit = puThread->ThreadInit(), 1);
  if (bProceedAfterInit) {
    // Init OK -> note we've started
    TRYTOPERFORM(puThread->m_eState = EThreadState::eBusy, 2);

    // Perform the action
    TRYTOPERFORM(puThread->m_puResult = puThread->Action(), 3);

    // Note we're done
    TRYTOPERFORM(puThread->m_eState = EThreadState::eFinishing, 4);

    // And do thread destruct code
    TRYTOPERFORM(puThread->ThreadEnd(), 5);
  }

  // Note the thread is done, and is safe to delete if needed
  TRYTOPERFORM(puThread->m_eState = EThreadState::eIdle, 6);

  // Look if any errors occured up till now
  if (nError != 0) {
    // Yes -> report this as well
    TRYTOPERFORM(puThread->m_eState = EThreadState::eError, 7);
  }

  // Look if the thread should auto-destruct
  bool bSelfDestruct = false;
  TRYTOPERFORM(bSelfDestruct = puThread->m_bAutoDestruct, 8);
  if (bSelfDestruct) {
    // Yes -> kill it now
    TRYTOPERFORM(delete puThread, 9);
  }

  // And return what happened
  return nError;
}