A class for worker threads

.
Copyright ) 1999, Bratislava, Slovakia, Europe

Worker Thread developing Guidelines

CThread class written in Microsoft Visual C++ is a wrapper
class that constitutes the base for the comfortable WINDOWS
worker thread handling in the MFC environment. CThread itself is
an abstract class from which user thread-specific classes have to
be derived. CThread class offers the basic opportunities how to
define, implement and handle thread objects. Most functionality
is done in this base class, the developer is just responsible to
implement the thread-specific task and handle incoming
notifications fired from the owner of the thread. CThread class
is fully compliant to the Object-Oriented Paradigm.

CThread Class Conception

Thread-task paradigms

CThread abstract class defines conception describing the main
requirements regarding the thread handling. There are two main
paradigms concerning thread task implementation:

Trivial Threads

Thread task is a simple sequence of commands that are to be
done. After starting the thread, the thread terminates after
completing the whole task. No any notification is accepted from
outside and no any activity status is provided for the owner
application. The only way how to communicate with a thread is to
use a user-defined object which is incorporated in the thread
handler body and call it’s methods from outside. This
communication, however, is not thread safe, no any
synchronization or an effective runtime notification feature is
guaranteed. It’s up to the developer responsibility to solve
all risky consequences coming from the concurrent thread
handling. Although it’s easy to implement this conception,
it’s recommended for simple, non-critical or single tasks
only. Thread supporting this paradigm is called Trivial Thread.

Notificable Threads

Thread task is application-sensitive and listens to the
application commands. In this case the thread task is a loop in
which thread waits for incoming notifications (commands). After
receiving a notification (command) the thread execute this
command. Simultaneously it should set the current thread activity
status to inform the thread-owner process. This is the most
recommended paradigm how to establish thread tasks. This thread
is called Notificable Thread.

CThread class supports both paradigms but prefers and
emphasizes developers to use the second one.

Thread Synchronization

Thread-Handler-Oriented Synchronization

CThread derived classes may utilize the synchronization
feature which is implemented in the base CThread class. That
means, developers do not deal too much with synchronization among
thread objects using the same thread-task handler (the
ThreadHandler() method). Developers just use Lock() or Unlock()
CThread methods to lock the critical code that is to be executed
exclusively in the ThreadHandler() method. Developers may,
however, omit the synchronization feature and define the CThread
derived class, which does not support this kind of
synchronization. It’s up to the developer’s responsibility
to implement non-critical thread task or instantiate just one
thread object of such class. Each CThread derived class requiring
its own synchronization must declare this feature explicitly
(this can be automatically established while working with Worker
Thread Class Generator Wizard).

This kind of synchronization is so-called
Thread-Handler-Oriented synchronization. Suppose a developer
utilizes two CThread derived classes CThreadDerived1 and
CThreadDerived2 and each class implements its own ThreadHandler()
method. Let Thread11 and Thread12 are the instantiated objects of
the class CThreadDerived1 and Thread21 and Thread22 are the
objects of the class CThreadDerived2. Thread11 is synchronized
with Thread12 but not with Thread21 or Thread22 (and vice versa).
All thread objects of the CThreadDerived1 class are synchronized
among each other (as well as all objects of the CThreadDerived2
class) but the synchronization of the CThreadDerived1 objects is
fully independent to the synchronization of the CThreadDerived2
objects.

On the other hand, if the CThreadDerived3 class is derived
from the CThreadDerived2 but do not implement the ThreadHandler()
method (uses the handler implemented in the CThreadDerived2
class), the CThreadDerived3 objects are automatically
synchronized with the CThreadDerived2 objects and vice versa.

Important Note 1:

If the CThreadDerived2 class implements the ThreadHandler()
method and the CThreadDerived3 class (derived from
CThreadDerived2) does not implement its own handler (utilizes the
same thread handler as the CThreadDerived2 class does – see
the next article) CThreadDerived3 inherits the synchronization
ability from CThreadDerived2. That means if CThreadDerived2
supports the synchronization, CThreadDerived3 objects utilizes
the same synchronization as CThreadDerived2 objects. If
CThreadDerived2 is not intended to use the synchronization at
all, CThreadDerived3 objects cannot utilize the synchronization
as well.

Although an establishing of the synchronization in
CThreadDerived3 objects in such case is theoretically possible
(by rewriting the generated code) developers should never do
that. Note that thread objects of both types use the same thread
handler, which means in consequence, that some of them will be
synchronized and some not. This conceptual idea was primarily
refused and was being claimed as forbidden.

Single Thread Object Synchronization

CThread class itself implements an internal synchronization.
This is the special synchronization feature that is not visible
from within the thread-owner process. Internal synchronization
does not impact Lock() or Unlock() mechanism mentioned in the
above paragraph under any circumstances. The benefit of the
internal synchronization is the synchronized access to the
critical CThread internal methods while accessing to one instance
of CThread object asynchronously.

This kind of synchronization is sometimes called Single Thread
Object Synchronization. If, for example, the childThread is an
instance of CThread-derived class, which is shared by two other
(arbitrary) threads parentThread1 and parentThread2, both these
parent threads operate on the childThread asynchronously. The
Internal Synchronization implemented in CThread class guarantees
that all critical childThread methods will be resolved properly
regardless the parent thread that owns the current
child-thread-task focus.

This kind of synchronization is established automatically
while registering CThread classs.

Process Synchronization

CThread-derived class offers also the global locking mechanism
which is exclusive for the whole process. The user may use
ProcessLock() or ProcessUnlock() CThread static methods (that
were previously opened by OpenProcessLocking() method) whereever
in the code. These methods are static so there is no necessary to
instantiate any CThread object. User may use this synchronization
mechanism to accomplish an exclusive access to the
global-critical resources (opening the file, common communication
object, singleton etc.). Process Synchronization may be used in
an arbitrary part of the code not necessarily in CThread tasks
only.

The mentioned synchronization does not support an
inter-process synchronization.

Thread Notification

Notificable Threads react to the thread-owner incoming
commands and allow the owner to obtain the current thread
activity status. Thread-owner process may use SendCommand()
CThread method which fires an appropriate command to the thread.
Thread immediately reacts to the incoming command (inside
ThreadHandler() virtual method) and is responsible to handle this
command. Simultaneously, the thread should set the current
activity status by using SetActivityStatus() method. Thread-owner
process uses GetActivityStatus() to obtain the current status.

Stackable Commands

The user should always communicate with CThread notificable
threads via Commands. Start(), Pause(), Continue(), Reset(),
Stop() or the general SendCommand() CThread methods are intended
for such use. Although, this is not the only way how to
communicate with CThread threads (we may, for example, use the
methods of the specific object operating in the thread handler
directly), it is highly recommended to use the Command
communication. The main benefit is a synchronized access to the
thread-critical code sections.

SendCommand() method supports cyclic stack mechanism. That
means, for example, the calling sequence: Start(), Pause(),
Reset(), Continue(), Stop() fired in one step will be handled
exactly in the same order in which they were called.

Generating CThread Derived Class

CThread derived classes can be generated automatically by
using the Worker Thread Class Generator Wizard enclosed in this
delivery. User may choose the base CThread derived class from
which he wants to derive his own CThread derived class, defines
its own thread handler and implements the thread specific task by
writing down the code in the generated source file. He may also
establish the specific class synchronization or share the
synchronization already established in the base class (see
‘Important Note’ in the previous article). The Worker
Thread Class Generator workaround does not require any special
explanation its intuitive at first glance.

Implementing CThread Task Handler

After generating the CThread derived class the developer has
to implement the thread handler at least in one such class (from
the Class Object Hierarchy point of view). The thread handler is
declared and implemented in the *.h and *.cpp files as a virtual
method: CThreadDerived::ThreadHandler().

According to the article 1 the user may implement one of the
two possible paradigms.

1. Trivial Thread: Simple sequence of
commands. Thread task implemented in such handler does not
receive any command from outside and does not notify the
thread-owner process. ThreadHandler() in this case looks very
simple as follows:


DWORD CThreadDerived::ThreadHandler()
{
	BOOL bEverythingOK;

	<Command_1>;
	<Command_2>;
	<Command_3>;
	...
	<Command_n>;
	...

	if (bEverythingOK)
		return CThread::DW_OK	;	// return 0 if finished successfully
	else
		return CThread::DW_ERROR;	// return –1 otherwise
}

2. Notificable Thread: ThreadHandler()
implements the task which is sensitive to the thread-owner
incoming commands. The thread-owner may obtain the current thread
activity status in an arbitrary phase of running thread.
‘Thread Handler()’ in such case should look as follows:


// CThread derived class supporting thread object synchronization
DWORD CThreadDerived::ThreadHandler()
{
	BOOL bContinue = TRUE;
	int nIncomingCommand;

	do
	{
		WaitForNotification(nIncomingCommand, CThreadDerived::DEFAULT_TIMEOUT);

		/////////////////////////////////////////////////////////////////////////////
		//	Main Incoming Command Handling:
		/////////////////////////////////////////////////////////////////////////////
		switch (nIncomingCommand)
		{

		case CThread::CMD_TIMEOUT_ELAPSED:
			SetActivityStatus(CThread::THREAD_PENDING);
			UserSpecificTimeoutElapsedHandler();
			Run();										// fire CThread::CMD_RUN Command if needed
			break;

		case CThread::CMD_INITIALIZE:		// initialize Thread Task
			// this Command MUST be handled; it’s fired when the Thread starts
			SetActivityStatus(CThread::THREAD_RUNNING);
			UserSpecificOnInitializeHandler();
			Run();										// fire CThread::CMD_RUN Command
			break;

		case CThread::CMD_RUN:					// handle 'OnRun' Command
			SetActivityStatus(CThread::THREAD_RUNNING);
			UserSpecificOnRunHandler();
			// the critical code which requires an exclusive handling (synchronization)
			Lock();										// CThread method
			UserSpecificCriticalCode();
			Unlock();									// CThread method
			break;

		case CThread::CMD_PAUSE:				// handle 'OnPause' Command
			if (GetActivityStatus() == CThread::THREAD_RUNNING)
			{
				SetActivityStatus(CThread::THREAD_PAUSED);
				UserSpecificOnPauseHandler();
			};
			break;

		case CThread::CMD_CONTINUE:			// handle 'OnContinue' Command
			if (GetActivityStatus() == CThread::THREAD_PAUSED)
			{
				SetActivityStatus(CThread::THREAD_CONTINUING);
				UserSpecificOnContinueHandler();
				Run();									// fire CThread::CMD_RUN Command if needed
			};
			break;

		case CThreadDerived::CMD_USER_SPECIFIC:	// handle the user-specific Command
			SetActivityStatus(CThreadDerived::THREAD_USER_SPECIFIC);
			UserSpecificOnUserCommandHandler();
			break;

		case CThread::CMD_STOP:					// handle 'OnStop' Command
			UserSpecificOnStopHandler();
			bContinue = FALSE;						// ... and leave thread function finally
			break;

		default:										// handle other Commands...
			break;

		};

	} while (bContinue);

	return (DWORD)CThread::DW_OK;						//... if Thread task completition OK
}

Establishing (… and starting) thread objects of the
CThreadDerived class in the thread-owner process as well as
handling these threads may look as in the following example:


CMainProgram::HandleThreads();
{
	CThreadDerived	thread1, thread2;

	// start threads
	try
	{
		thread1.Start();
		thread2.Start();

		...
		thread1.Pause();
			...
		thread2.Continue();

		// ... or send the user-specific command...
		// (must be recognizable in ThreadHandler())
		thread2.SendCommand(CThreadDerived::CMD_USER_SPECIFIC);

		...
		// stop threads
		thread1.Stop();		// (synchronous) waits until the thread actually finishes
		thread2.Stop();		// (synchronous) waits until the thread actually finishes
	}
	catch (CThreadException* pe)
	{
		if (!pe->GetErrorMsg().IsEmpty())
			pe->ReportError();
		pe->Delete();
	};
}

Note 2:

The communication from the thread-owner process to the
Notificable Thread should always be established by sending the
commands that are recognizable in the ThreadHandler() method.

Note 3:

Phrases using italic font in the mentioned source code list
mean CThreadDerived specific methods or data members. All others
are WINDOWS System functions or CThread provided methods and data
members.

Remark

The user may utilize CThread-constructor parameters while
constructing a CThread object. The first parameter is a void
pointer and the second is LPARAM value. Void pointer may point to
an arbitrary useful object (an owner of this thread e.g.). During
thread task operation the thread may notify the owner object if
needed. For example, the running thread may set the task progress
position in the progress bar implemented in the owner object.
Thread must, of course, be familiar with the architecture of the
owner object.

Before Starting the Work

CThread features mentioned on this page are introduced in very
general fashion only. Before starting the work user should
download and unzip the enclosed ‘CThread.zip’ file on the local
machine where the whole necessary code, documentation and the
code wizard will be placed. After the installation developers may
utilize Developor.doc documentation or CThread.hlp Reference
Manual. Documentation provides detailed information how to deal
with CThread objects offering examples as well as an exact
description of all CThread methods. This documentation can be
found in ‘Doc’ subdirectory of the main installation directory.

Documentation

More detailed information concerning CThread class can be
found in CThread.hlp or CThread.htm Reference Manuals in ‘/Doc’
subdirectory of the main installation directory.

Unzipping ‘CThread.zip’

‘CThread.zip’ file should be unzipped to the empty directory
(installation directory) on the local machine. It automatically
expands to the desired subdirectory hierarchy.

Subdirectory Contents:

  • <root> – Worker Thread Class Generator Wizard
    (ThreadGenerator.exe)
  • /BaseClass – contains CThread.h and CThread.cpp
    implementation files of the base abstract CThread class.
  • /Doc – Developers Guidelines and the CThread Reference
    Manual
  • /Templates – source file templates used by the Worker
    Thread Class Generator Wizard

Download source – 172 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read