Contents
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 opportunities how to define, implement and handle thread objects. Most functionality is done in this base class, a developer is just responsible to implement a 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 abstract class defines conception describing the main requirements regarding the thread handling. There are two main paradigms concerning thread task implementation:
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. Owner thread (typically the main application thread) may communicate with a CThread thread by some intermediate object visible for both sides. This communication, however, does not provide effective parent-child-thread notifications. Maintaining all risky situations originating in such communication requires an additional developer's effort. Using of this conception is recommended for linear or straightforward heavyweight tasks, more or less independent from the owner thread that do not require sophisticated or intensive communication with the owner thread. CThread thread supporting this paradigm is called Trivial Thread.
In opposite to Trivial Threads, the thread task may be application-sensitive and listens to the owner thread commands. In this case the thread task is a loop in which thread waits for incoming notifications (commands). After receiving a command the thread executes this command. Incoming commands are handled sequentially in the thread task procedure. Simultaneously, the thread may set the current thread activity status to inform the owner thread.
Notificable Threads act as 'schedulers' or 'services'. That means, these threads execute their task on the owner thread demand and wait for another command. Usually the task executed in such thread should not be too long to allow an owner thread to make an effective controlling over the thread. This thread is called Notificable Thread.
CThread class supports both paradigms but emphasizes developers to use Notificable Threads.
CThread derived classes may utilize the special synchronization feature that is implemented in the basic CThread class. The mentioned Thread-Handler-Oriented Synchronization is a powerful feature provided by CThread class. Developers do not have to deal too much with synchronization among thread objects using the same thread-task handler (the ThreadHandler() method). They 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 this synchronization feature and define the CThread derived class, which does not support this kind of synchronization. It is up to the developer’s responsibility to implement non-critical thread task or instantiate just one thread object in such case.
Each CThread derived class requiring its own Thread-Handler-Oriented 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. 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 CThreadDerived1 objects is fully independent from the synchronization of CThreadDerived2 objects.
On the other hand, if the CThreadDerived3 class is derived from the CThreadDerived2 but do not implement its own ThreadHandler() method (uses the handler implemented in the CThreadDerived2 class), the CThreadDerived3 objects are automatically synchronized with the CThreadDerived2 objects and vice versa.
In a CThread object-oriented hierarchy developers may design several CThread -derived classes for different purposes. Some of them will implement the specific thread-task handlers the others will inherit them. Thread-Handler-Oriented Synchronization allows developers to split CThread objects into the groups that contain CThread -derived objects operating on the same thread-task handler. Thus, each group defines its own thread-task handler and logically provides the synchronization for all objects belonging to this group. On the other hand, another group operates on a different thread-task handler, executing a completely different task independent from another group, so the synchronization has also to be independent from another group. Thread-Handler-Oriented Synchronization establishes and maintains the independent synchronization for each group automatically. All the developer has to do is to write the SUPPORT_THREAD_SYNCHRONIZATION(ClassName) macro in the constructor in his CThread-Derived ClassName class where the thread-task handler (the virtual ThreadHandler() method) is actually implemented. Thread-Handler-Oriented Synchronization is supported for both Trivial and Notificable CThread Threads.
CThread class itself implements an internal synchronization. This is the special synchronization feature that is not visible from within the owner thread. Internal synchronization does not impact Lock() or Unlock() mechanism mentioned in the above paragraph under any circumstances. The benefit of the internal synchronization is a synchronized access to critical CThread methods while accessing the 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 object asynchronously. The internal synchronization implemented in CThread class guarantees that all critical childThread methods will be executed properly regardless the parent thread owning the current childThread focus. The only care that must be taken is that none of the parent threads deletes the childThread's CThread object while another is still operating on it.
Despite the given guaranty there is one situation where the concurrent operating on the same object may be confusing. It concerns mainly Notificable Threads and sending commands from the parent threads to the childThread. If both parent threads send several commands asynchronously to the childThread there is no any guaranty about which command will be actually handled in the childThread at a moment. The similar situation may occur also in Trivial Threads while setting up some important controlling object property (or variable) used in the childThread by both parent threads concurrently. For this reason, a concurrency-handling-of-one-object design has to be carefully prepared.
This kind of synchronization is established automatically while registering CThread class in an application process for both Trivial and Notificable Threads.
CThread-Derived class offers also a 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) wherever in a 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.
Notificable Threads react to the owner-thread incoming commands and allow the owner to obtain the current thread activity status. Owner thread usually uses the PostCommand() 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 meaningful current activity status by using SetActivityStatus() method. Owner thread uses GetActivityStatus() method to obtain the current status.
To establish a Notificable thread a developer has to add the SUPPORT_THREAD_NOTIFICATION macro in his CThread-Derived class constructor where the thread-task handler (the virtual ThreadHandler() method) is actually implemented. He also has to implement the thread-task handler following the specific rules discussed later. The whole stuff may be arranged automatically by using the Worker Thread Class Generator Wizard that is discussed in the chapter 2.
The user should always communicate with CThread notificable threads via commands. Start(), Pause(), Continue(), Reset(), Stop() or the general PostCommand() 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 methods of a specific object operating in the thread handler directly), it is highly recommended to use the mentioned command-communication. The main benefit is a synchronized and logical access to the thread-task code. Incoming commands fired from everywhere fill up an internal CThread command queue and are handled sequentially in the same order they were fired - first-in-first-out (cyclic stack mechanism). For example, the calling sequence: Start(), Pause(), Continue(), Stop() fired in one step will be handled exactly in the same order in which they were called.
CThread class introduces four helper methods wrapping the PostCommand() method: Run(), Pause(), Continue() and Reset(). All they do is just firing the corresponding command to a CThread thread. These methods just remind the similar functionality schema provided by schedulers or services. Developers may decide how to interpret these methods or they may decide not to utilize them at all. What is important is to handle an appropriate command in the ThreadHandler() method when a developer decides to use these helpers.
To avoid racing discrepancies between threads, developers should follow several guidelines while building an application using CThread threads. An owner thread is responsible for a CThread thread lifetime. From the child CThread thread point of view, thus, the owner thread exists during the whole thread lifetime. The only responsibility for such thread is not to destroy the owner thread explicitly.
On the other hand, from the owner-thread point of view, CThread thread may terminate unpredictably. CThread object, however, guarantees that each CThread method call will be semantically properly executed regardless the attached Windows thread is alive or not. In other words, none CThread method will hang after the thread will have been prematurely terminated.
The owner thread (or any thread) may utilize some CThread methods to find out the existence status of the thread (IsAlive(), GetExitCode(), GetActivityStatus() etc.). Furthermore, the owner thread may be notified by some user specific callback that is initiated from within the CThread thread to be invoked immediately when some important situation occurs. Similarly, this thread may set up a useful variable (or an object property) which is shared by the owner thread as well (in Trivial Threads).
For CThread threads cooperating among each other it is a developer's responsibility how to maintain racing conditions. This usually strongly depends on the concrete architecture of an application. In general, avoiding racing problems requires a proper thread-communication design and an additional developing effort. Developers may also consult some design-patterns discussing this theme.
There is one big dilemma in a thread theory - "use or not to use" synchronous methods for controlling of threads. An advantage of such schema is a guaranty that each method sending a command to a child thread invoked from the owner thread waits until the child thread actually completes the command. If, for example, the thread is forced to be paused by some, for example, PauseThread() method called from the owner thread, PauseThread() will wait until the thread is actually paused. This works fine for many worker threads that are more or less independent from their owner threads. Usually, SetEvent-WaitForSingleObject programming model is used in this schema.
Unfortunately, there are many cases when this schema is inapplicable. In GUI applications requiring bi-directional communication between an application and a thread this model is a priori inappropriate. Following the previous example, if a GUI application calls the PauseThread() method it may wait a long time for the thread completion which leads to a message queue blocking. In this situation the application hangs until the method is completed. Moreover, if for any reason the PauseThread() method hangs or fails the application may hang forever.
Even worse situation occurs when the thread tries to cooperate with the main GUI application using the PostMessage() and SendMessage() functions. If PauseThread() is invoked in the application and the thread sends consequently some message back to the application waiting for a response, this message cannot be handled in the application. The message is namely to be handled by the application thread but the application thread is blocked by the PauseThread() method. This is a serious situation leading to an excellent deadlock. The only safe using of a synchronous thread-controlling method in a GUI application is the case when the thread does not require any task to be executed by the application thread while completing the appropriate command.
CThread class uses synchronous methods as few as possible. There are just four methods that works synchronously: Start() and Kill() methods that are, fortunately, not dangerous because of their principal independence from the owner thread, and Stop() and WaitForActivityStatus() methods for which a special care has to be taken. For more information concerning these methods refer to CThread Reference Manual (CThread.hlp or CThread.htm).
Instead of distinguishing among many possible scenarios how to manage synchronous calls, CThread class provides only one general method for such purposes: WaitForActivityStatus(). This method is called from the owner thread and just waits until the thread sets up a desired thread activity status. The method, however, requires a proper synchronous-call design varying from case to case (see CThread Reference Manual).
Stopping CThread threads is discussed in details in the next paragraph.
CThread object wraps a Windows worker thread. Nevertheless, the thread may be used in GUI context as well. As a worker thread CThread object does not contain its own message queue and therefore shares the same queue as the main application thread. Thread itself communicates with this queue (or a Windows window procedure directly) usually via the PostMessage() and SendMessage() functions or methods utilizing these functions implicitly. Using thePostMessage() function is generally considered as more safe.
Sending messages from the CThread thread to the main application message queue is preferred by using the Windows function PostMessage(hWnd, nMsg, lParam, wParam) requiring the original Windows HWND handle of the main application window. Generally, there is no guaranty about lifetime of C++ objects originating in a different thread. These C++ objects have to be handled carefully in the CThread context.
Normally, there is not big problem while communicating with the message queue belonging to the main application window (if no synchronous thread-controlling methods are called from the main thread - see the previous paragraph). CThread thread posts messages to the main window in the same way as other objects do in the main application. Posted message is added at the end of the queue and sequentially handled. However, there is one important exception - stopping the thread. Stopping the CThread thread is accomplished by the CThread's Stop() method called from the main application thread. Stop() method is primarily a synchronous method and should wait until the CThread thread actually finishes. If the CThread thread is forced to stop from within the application but is still communicating with the application thread or the message queue (and eventually waiting for message completion - some methods like the List Control's DeleteAllItems() use implicit SendMessage() notification), that thread may not be properly terminated. The thread waits for the message completion that is to be done in the main application thread but the application thread itself is blocked by the Stop() method in some message handler. This leads to a deadlock and the application freezes.
To get out of such difficulties we have to use one 'tricky' method. Suppose we are using the CThread thread communication with the application message queue (or, generally, with the application thread, e.g. through SendMessage()). First of all, a developer has to define a special activity status, for example, THREAD_PREPARED_TO_TERMINE in his CThread-Derived class. This activity status describes the situation that the CThread thread having set up this activity status will not utilize neither the application message queue nor the application thread any more, thus, from this moment the thread will neither post nor send any Windows message to the application. The fragment of code setting up this activity status in the CThread's ThreadHandler() method is as follows:
...
case CThread::CMD_RUN:
SetActivityStatus(CThread::THREAD_RUNNING);
// communicate with the main application message
// queue/application thread
PostOrSendMessagesToTheApplication();
break;
...
case CThread::CMD_STOP:
// stop communication with the application
CompleteCommunicationFinally();
// and set up the necessary status informing the application
// that this thread will not utilize the message queue/thread any more
SetActivityStatus(CThreadDerived::THREAD_PREPARED_TO_TERMINE);
bContinue = FALSE;
break;
....
The next step is to stop the CThread thread from within the main application (owner thread). The best way is to use the main frame's OnClose() method in main-frame applications (in dialog-based applications the situation is described as well). The main idea is to suppress closing the main frame window until the CThread thread's activity status is equal to THREAD_PREPARED_TO_TERMINE. This is accomplished by periodical firing the WM_CLOSE message from within the OnClose() method to prevent an application-thread-blocking and allowing the CThread thread to complete its eventual message handling. The whole mentioned stuff is accomplished in the following code in the main application:
For main-frame-based applications (SDI, MDI):
CollapseCMainFrame::OnClose()
{
DWORD dwExitCode;
// force the thread to stop but leave immediately
// (the second parameter = 0)
m_myThread.Stop(dwExitCode, 0);
if (m_myThread.GetActivityStatus() !=
CThreadDerived::THREAD_PREPARED_TO_TERMINE)
{
// invoke this handler again (keeping the message
// loop enabled for the final
// CThread thread message handling as well as
// preventing the application thread
// from blocking)
PostMessage(WM_CLOSE);
}
else
{
// here the <CODE>CThread thread doesn't use
// the message queue/application thread
// anymore, so we can stop the thread synchronously
// to ensure that the thread
// is actually terminated
m_myThread.Stop(dwExitCode);
// ... and close the main frame properly
CFrameWnd::OnClose();
};
}
Analogically for dialog-based applications (we will use OnCancel() virtual method here - this method is not a message handler!):
CMyDialog::OnCancel()
{
DWORD dwExitCode;
m_myThread.Stop(dwExitCode, 0);
if (m_myThread.GetActivityStatus() != CThreadDerived::THREAD_MSG_SESSION_CLOSED)
{
// call this handler again
// IDCANCEL is the dialog's 'Cancel' button resource ID
PostMessage(WM_COMMAND, MAKEWPARAM((WORD)IDCANCEL, (WORD)IDCANCEL));
}
else
{
m_myThread.Stop(dwExitCode);
// ... and close the dialog properly
CDialog::OnCancel();
};
}
In some specific cases more complex handling is needed. Developers may want to use the special flag indicating the end of message handling initiated in the CThread thread. They may also want to stop the thread in some other non-finalize method. But the basic idea not to block the application thread and periodically call the stop-thread-handler by the PostMessage() method sketched in the above code remains the same.
CThread-Derived classes can be generated automatically by using the Worker Thread Class Generator Wizard enclosed in this delivery. A user may choose the base CThread derived class from which he wants to derive his own Trivial or Notificable CThread-Derived class and implements the thread specific task by writing down the code in the ThreadHandler() method skeleton. He may also establish some kind of synchronization or share the synchronization already established in the selected base class. The Worker Thread Class Generator workaround does not require any special explanation and it's intuitive at first glance.
After generating the CThread-Derived class source 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. ThreadHandler() in this case may look 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 owner thread incoming commands. The owner thread may, in turn, obtain the current thread activity status in an arbitrary phase of running thread. Thread Handler() in such case should look somehow like this:
Collapse// 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:
if (GetActivityStatus() != CThread::THREAD_PAUSED)
{
UserSpecificTimeoutElapsedHandler();
// fire CThread::CMD_RUN command immediately...
// ... from within this thread use always the
// following method when you
// want to fire an additional command.
// This method bypasses all incoming
// commands and forces the passed command
// to be handled immediately
HandleCommandImmediately(CMD_RUN);
}
break;
case CThread::CMD_INITIALIZE: // initialize Thread Task
// this command should be handled; it’s fired when the Thread starts
UserSpecificOnInitializeHandler();
// execute CThread::CMD_RUN command immediately
HandleCommandImmediately(CMD_RUN);
break;
case CThread::CMD_RUN: // handle 'OnRun' command
if (GetActivityStatus() != CThread::THREAD_PAUSED)
{
SetActivityStatus(CThread::THREAD_RUNNING);
UserSpecificOnRunHandler();
// the critical code which requires an exclusive handling
// for all threads operating on this handler
// (Thread-Handler-Oriented Synchronization)
Lock(); // <CODE>CThread method
UserSpecificCriticalCode();
Unlock(); // CThread method
}
break;
case CThread::CMD_PAUSE: // handle 'OnPause' command
if (GetActivityStatus() != CThread::THREAD_PAUSED)
{
UserSpecificOnPauseHandler();
SetActivityStatus(CThread::THREAD_PAUSED);
}
break;
case CThread::CMD_CONTINUE: // handle 'OnContinue' command
if (GetActivityStatus() == CThread::THREAD_PAUSED)
{
SetActivityStatus(CThread::THREAD_CONTINUING);
UserSpecificOnContinueHandler();
// execute CThread::CMD_RUN command if necessary
HandleCommandImmediately(CMD_RUN);
}
break;
case CThreadDerived::CMD_USER_SPECIFIC:
// handle the user-specific command
UserSpecificOnUserCommandHandler();
break;
case CThread::CMD_STOP: // handle 'OnStop' command
UserSpecificOnStopHandler();
bContinue = FALSE; // ... and leave the thread function finally
break;
default: // handle unknown commands...
break;
};
} while (bContinue);
return (DWORD)CThread::DW_OK; //... if thread task completion OK
}
Establishing (and starting) thread objects of the CThreadDerived class in the owner thread 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 the ThreadHandler())
thread2.PostCommand(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)
{
pe->ReportError();
pe->Delete();
};
}
Note 1:
The communication from the owner thread to the Notificable Thread should always be established by sending the commands that are recognizable in the ThreadHandler() method.
Note 2:
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.
- A 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.) or to be a HWND Window handle with which the CThread thread cooperates. 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.
- While using
CThread -threads users should not use those Windows thread-specific functions that are responsible for creation or termination of Windows threads in the CThread context. Keep in mind that CThread threads are maintained by the specific CThread architecture. Creating threads in the terms of CThread class means various internal allocations, settings and synchronization-registering (in the Thread-Handler-Oriented Synchronization model) that are correctly removed only by using the specific CThread operations (leaving the thread controlling function, CThread class destructor, Stop() and Kill() methods). Termination of a thread by e.g. ::TerminateThread() Windows function, bypasses the unregistration model provided by the CThread class which may result to several deadlocks or, even, to a crash. The following list contains Windows thread-specific functions that should not be used in the CThread context:
CreateThread()
CreateRemoteThread()
ExitThread()
TerminateThread()
ThreadProc()
CloseHandle() (this especially may lead to a catastrophic failure)
Users may, of course, utilize the mentioned functions but under their own thread strategy omitting CThread features.
Users should also prefer the CThread::GetExitCode() method instead of GetThreadExitCode() Windows function. This method offers the valid thread-exit-code regardless the Windows thread is alive, destroyed or closed. The method works fine even if the handle belonging to the Windows thread is not more valid.
Although CThread class provides the GetHandle() method returning a Windows handle of a running thread, this method must be used very carefully - if ever. CThread architecture considers this value as an internal variable. The handle is automatically closed (and zeroed) after the thread leaves the thread controlling function. In general, the state of the handle is, therefore, unpredictable. Developers, therefore, have to take care about using the Windows thread handle regarding the thread lifetime in a specific application.
Opened CThread session must be properly "closed". That means, all CThread -specific internal allocations and registering are to be closed at the end of the CThread session. CThread class itself accomplishes this stuff automatically when the corresponding Windows thread naturally finishes (leaves the thread controlling function mapped to the CThread's ThreadHandler() method). Otherwise CThread object provides three possibilities how to stop the running thread and make the necessary clean up automatically. Users should always use only the following strategies while stopping the CThread thread explicitly:
- CThread's
Stop() method forces the thread to the regular stopping and the clean-up is accomplished automatically after leaving the ThreadHandler() method. The best way.
- CThread's
Kill() method destroys the running Windows thread and cleans up the whole contents on its own. Possible, but should be used in emergency cases only.
CThread object going out of scope invokes a destructor that kills the Windows thread by calling the Kill() method (see the previous point). Ugly not recommended way.
More detailed information concerning CThread class can be found in CThread Reference Manual (CThread.hlp, CThread.htm and CThread.doc files) in '/Doc' subdirectory of the main installation directory.
Author: Dominik Filipp, © 1999, Bratislava, Slovakia, Europe
E-mail address: Dominik.Filipp@work4.sk
To display a list of topics by category, click any of the contents entries below. To display an alphabetical list of topics, choose the Index button.
C/C++ Elements
Classes and Class Members
Structures and Enums
Other
Overviews
Modules
Help file built: 08/30/99
About Autoduck
The sources for this Help file were generated by Autoduck, the source code documentation tool that generates Print or Help files from tagged comments in C, C++, Assembly, and Basic source files.
For more information, contact Eric Artzt (erica@microsoft.com).