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.
CThread provides easy multi-threading support to your application, where each thread is maintained by a separate object.
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.
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.
Each file belonging to this source code module is listed below.
/*******************************************************************************
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
/*******************************************************************************
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;
}