Basics Of Thread Programming – Part 3
Introduction to MFC Thread Programming
MFC wraps the windows API for threading in a partial object oriented
way. MFC has defined two kinds of threads based on the usage patterns. They
are Worker threads and User-interface threads. Worker threads are useful
for the jobs that will do work in the background without any relation
to events and user interactions. User-Interface thread are ideal for those
kind of jobs that will relate with events, messages or user interaction for
their processing.
Worker Threads
Here are the most important features of MFC worker threads.
- Threads are to be created using one of the versions of AfxBeginThread.
- AfxBeginThread takes the pointer to function as one of the parameters.
It will start a new worker thread and makes the worker thread execute
the function whose pointer is obtained as parameter.
- Other parameters to the function are the parameters to the function
to be executed by the thread and the operational properties of the
thread like priority, stack size, state of thread after creation etc.
Here is the prototype of the method for worker thread.
CWinThread* AfxBeginThread( AFX_THREADPROC
ptrToFunction, LPVOID parameterToFunction, int threadPriority = THREAD_PRIORITY_NORMAL,
UINT stackSizeForThread = 0, DWORD threadCreationFlags = 0, LPSECURITY_ATTRIBUTES
securityAttributes = NULL);
- Worker thread will terminate once the thread function is completely
executed and control returns from it. So a simple return statement will terminate
the execution of the thread.
- Other way of terminating the thread is by making the thread (to be
terminated) to execute AfxEndThread() function.
- Also Worker threads can be terminated using Win32 functions but it
may not be safe to do so according to the context.
- If a thread has to be terminated based on some condition or based
on the state of the system, then different kinds of techniques can be applied
like polling to the value of some variable(s) and based on the value
of the variable, the course of thread execution can be changed.
Following is the screen shot of the sample application that creates
2 worker threads that update the progress bars from time to time.
“Start Threads” button will create 2 threads with different methods
to execute. Those methods will change the progress bar from time to time.
The method will also poll to the value of a variable which when set to false,
returns from the method , resulting in termination of the thread. “Stop
Threads” will set the variable to false forcing the method to terminate.
“OK“ button will also do the same thing, but in addition it also terminates
the application.
Here are the steps for making the sample application. These steps show only
the threading part and not all the application. For complete application,
refer to the complete code sample associated with this tutorial. It is assumed
that the user has the knowledge of MFC and it’s basic programming like creating
skeleton application, creating GUI, event handling etc. There are a lot of
tutorials related to these basics in MSDN software and website.
Creation Of MFC Application & GUI
- Create a dialog based MFC application from the wizard.
- From resource editor add to the dialog, 2 progress bars and 3 buttons
as shown in the above figure.
- Also create 2 control member variables to the dialog class for the
progress bars and name them as firstProgressBar and secondProgressBar.
- Create the button click event handlers for the 3 buttons shown in the
above figure.
Creation Of Thread Functionality
- Create two bool member variables to the dialog class runFirstThread
and runSecondThread, which act as flags to indicate if the threads have to
run or stop.
- Define the method for the first thread in the dialog’s .cpp file as
following.
UINT showFirstProgress( LPVOID pParam ){
int i=0;
CMTWorkerDlg* mtWorkerDlg = (CMTWorkerDlg *)pParam;
//if flag is set to false, then go to return statement to terminate the thread
while(mtWorkerDlg->runFirstThread == true) {
//let the progress value oscillate
between 0 and 100
i = (++i)%101;
//update the status value to the progress bar
mtWorkerDlg->firstProgressBar.SetPos(i);
//introduce some delay so that the progress will
change visually
::Sleep(30);
}
return(1);
}
This method will be executed by the first worker thread. It casts
the void pointer to the dialog class. Then from time to it checks the value
of the variable runFirstThread. If it is true then it increases the progress
bar status and sleeps for some time, so that the progress will be incremented
slowly and visibly. It takes care that the progress don’t overflow 100 which
is the default maximum value of progress bar. If runFirstThread is
found to be false , then it’s an indication that the thread has to terminate
and the method will be returned, making the thread executing this method
to terminate.
- Similarly define the method for the second thread in the dialog’s .cpp
file as following.
UINT showSecondProgress( LPVOID pParam
){
int i=0;
CMTWorkerDlg* mtWorkerDlg = (CMTWorkerDlg *)pParam;
//if flag is set to false, then go to return statement
to terminate the thread
while(mtWorkerDlg->runSecondThread == true)
{
//let the progress value oscillate
between 0 and 100
i = (++i)%101;
//update the status value to the progress bar
mtWorkerDlg->secondProgressBar.SetPos(i);
//introduce some delay so that the progress will
change visually
::Sleep(30);
}
//return the function to terminate the thread executing
this method
return(1);
}
This works same as showFirstProgress() function but with the
second progress bar and with a different flag runSecondThread.
- Add the following code to the button click event handler function of
“Start Threads” button.
…………………………………………
//set the first thread's flag to run the thread
runFirstThread = true;
//set the second thread's flag to run the thread
runSecondThread = true;
//start the first thread with first progress bar
and high priority
AfxBeginThread(showFirstProgress, this, THREAD_PRIORITY_ABOVE_NORMAL
);
//start the second thread with second progress bar
and low priority
AfxBeginThread(showSecondProgress, this, THREAD_PRIORITY_BELOW_NORMAL
);
…………………………………………
This code will set the flags for the threads to run and continuously
update the progress bars. And it will start the threads with respective
progress bars. “this” is to pass the pointer to the current dialog class
to the thread methods which access the progress bars through it.. then the
first thread is started with high priority and the second thread with low
priority.
- Add the following code to the button click event handler function of
“Stop Threads” button.
…………………………………………
//set the first thread's flag to stop the thread
runFirstThread = false;
//set the second thread's flag to stop the thread
runSecondThread = false;
//reset the progress of the first progress bar to
zero
firstProgressBar.SetPos(0);
//reset the progress of the second progress bar to
zero
secondProgressBar.SetPos(0);
…………………………………………
This code will set the flags for the threads to stop their execution
immediately. Also it will set the status of the progress bars to zero.
- Add the following code to the button click event handler function of
“OK” button.
…………………………………………
//set the first thread's flag to stop the thread
runFirstThread = false;
//set the second thread's flag to stop the thread
runSecondThread = false;
//Let this thread wait for some time, so that those
threads will terminate in that time
::Sleep(40);
//Invoke default event handler to terminate the application
OnOK();
…………………………………………
This code will set the flags for the threads to stop their execution
immediately. And it will wait for some time, so that those threads
will terminate in that time. Then it invokes the default event handler for
“OK” button that will terminate the application.
Refer to the complete working sample of the Worker threads accompanied
with this article.
User-Interface Threads
Here are the most important features of MFC User-Interface threads.
- Threads objects are to be created using the subclass of CWinThread
class.
- Threads are to be created and started using CWinThread::CreateThread
method and also by one of the versions of AfxBeginThread method.
- AfxBeginThread takes the pointer to the runtime class object of the
CWinThread derived class. Other properties for the thread’s functionality
like the priority, stack properties etc can be set with other parameters
of the function.
- The object oriented way of creating the threads is using the CreateThread()
method of CWinThread object. It creates the thread and makes the thread execute
InitInstance() method of the CWinThread dericed class.
- So InitInstance() method should be overloaded with the functionality
the thread has to execute. Once the control exits from this method, the thread
terminates it’s execution.
- Other way of terminating the thread is by making the thread execute
AfxEndThread() function.
- Also the threads can be terminated using Win32 functions but it may
not be safe to do so depending on the context.
- The greatest advantage of using User-Interface threads over worker
threads is User-Interface threads can have message pumps. So the thread can
be informed what it is expected to do at any point of time. So the thread
can be manipulated in what ever the way it needs to be.
- If a thread has to be terminated based on some condition or based
on the state of the system, then a lot of techniques like event notification
, message passage etc. can be used.
In the current article Event Notification technique is demonstrated. In the
coming articles usage of message handling for inter thread communication
will be demonstrated.
Following is the screen shot of the sample application that creates
2 User-Interface threads that update the progress bars from time to time.
“Start Threads” button will create 2 threads with 2 CWinThread
derived class objects. These threads will change the progress of the progress
bars from time to time. While updating the progress bar, they will check
for the event that tells them to stop their execution. “OK” button
will set the event in it’s event handler which will tell the thread
to stop there, and then the application will terminate.
Here are the steps for making the sample application. These steps show only
the threading part and not all the application. For complete application,
refer to the complete code sample associated with this tutorial. It is assumed
that the user has the knowledge of MFC and it’s basic programming like creating
skeleton application, creating GUI, event handling etc. There are a lot of
tutorials related to these basics in MSDN software and website.
Creation Of MFC Application & GUI
- Create a dialog based MFC application from the wizard.
- From resource editor add to the dialog, 2 progress bars and 4 buttons
as shown in the above figure.
- Also create 2 control member variables to the dialog class for the
progress bars and name them as firstProgressBar and secondProgressBar.
- Create the button click event handlers for the 4 buttons shown in the
above figure.
Creation Of Thread Class
- Create a new class MyThread deriving it from CWinThread. This is the
actual thread class which we create to update the progress bars from time
to time. This class is planned so that each object of this class will keep
take care of a single progress bar.
- Add the following variables to the class to keep track of the progress
bar and it’s current progress value.
.......................................
//keep track of the current progress value of the progress bar managed by
this thread
int count;
//pointer to the the progress bar managed by this thread
CProgressCtrl* pProgressBar;
//Handle to Event object to indicate that the thread has to stop it's execution
HANDLE threadEventKill;
.......................................
- Add the MFC macro “DECLARE_MESSAGE_MAP()” to the header file of the
class. To the .cpp file add the following code.
.......................................
IMPLEMENT_DYNAMIC(MyThread, CWinThread)
.......................................
BEGIN_MESSAGE_MAP(MyThread, CWinThread)
//{{AFX_MSG_MAP(CGDIThread)
// NOTE - the ClassWizard will add and remove mapping
macros here.
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
.......................................
This fulfills the requirement of
MFC thread class for instantiation and message pumping.
- Create or overload the constructer of the class to do the basic initialization
like setting progress bar, setting flags etc.
MyThread::MyThread(CProgressCtrl* pProgressBar)
{
//This flag will destroy the thread object
if the thread is terminated
m_bAutoDelete = TRUE;
this->pProgressBar = pProgressBar;
//Create the event object which will indicate the thread to stop it's execution
threadEventKill = CreateEvent(NULL, TRUE, FALSE, NULL);
}
m_bAutoDelete is a CWinThread member that destructs the thread
object if the thread is terminated. With CreateEvent() method, the event
is created and initialized to the false state because of it’s 3rd parameter.
If it is set to true then it indicates that the event is raised to stop the
thread.
- In destructor destroy the event object.
MyThread::~MyThread() {
CloseHandle(threadEventKill);
}
- Following are the CWinThread methods that are overloaded. They can
be used for doing additional processing, but here they don’t do anything
except delegating to the default fuctionality implemented by CWinThread
void MyThread::Delete() {
CWinThread::Delete();
}
void MyThread::SuspendThread() {
CWinThread::SuspendThread();
}
void MyThread::ResumeThread() {
CWinThread::ResumeThread();
}
- Implement a method which increments the progress of the progress bar
continuously. It takes care that the value wont overflow by oscillating the
progress value between 0 and 100. Sleep will make the progress to change
visibly.
void MyThread::ShowStatus() {
//Oscillate the value between 1 and 100
count = (count+1)%101;
//update progress bar
this->pProgressBar->SetPos(count);
//introduce some delay so that the progress changes become
visible
::Sleep(100);
}
- Now implement InitInstance() method which determines the lifetime of
the thread. Thread will live as long as it executes the code of this method
or the code of the methods called from this method.
BOOL MyThread::InitInstance( ) {
count = 0;
//Is the event raised to stop/kill thread?
while (WaitForSingleObject(threadEventKill, 0) == WAIT_TIMEOUT)
//If not, then update the
progress bar
ShowStatus();
// if raised, return from this method, resulting in termination
of the thread
return(FALSE);
}
In a loop it checks if the event is raised for killing/stopping
the thread using WaitForSingleObject() method. This method will check for
the event. If the event is not raised, then the method will return the flag
WAIT_TIMEOUT indicating the request has timed out. If it times out then it
will update the progress by one step with the call to ShowStatus() method.
As long as the event is not raised it will continuously update the progress
bar. If the event is raised then the method will return, which cases the
thread to terminate and this object to destroy.
- Now create a method KillThread() which will implement the functionality
to stop/kill the thread by raising an event. This method will raise the event
with SetEvent() method. Before killing the thread, it will dereference the
pointer to the progress bar. This method will be used by the GUI to tell
to kill the thread.
void MyThread::KillThread() {
this->pProgressBar = NULL;
//raise the event to stop/kill the thread
VERIFY(SetEvent(threadEventKill));
}
Connect the GUI to the Threads
- Create 2 pointers of the Thread class in the dialog class which will
represent 2 threads.
................................
MyThread* pMyThread1;
MyThread* pMyThread2;
................................
- Instantiate the thread class with the pointers, do this as part
of the initialization.
................................
pMyThread1 = new MyThread(this, &firstProgressBar);
pMyThread2 = new MyThread(this, &secondProgressBar);
................................
- In the button click event handler of “Start” button , create the threads
and let them run. After creation of threads set their priorities.
................................
// create the threads and start them
pMyThread1->CreateThread();
pMyThread2->CreateThread();
//after creation set their priority to high and low
pMyThread1->SetThreadPriority(THREAD_PRIORITY_HIGHEST);
pMyThread2->SetThreadPriority(THREAD_PRIORITY_LOWEST);
................................
- In the button click event handler of “Suspend” button , call the methods
to suspend the threads.
................................
pMyThread1->SuspendThread();
pMyThread2->SuspendThread();
................................
- In the button click event handler of “Resume” button , call the
methods to resume the threads.
................................
pMyThread1->ResumeThread();
pMyThread2->ResumeThread();
................................
- In the button click event handler of “OK” button, call the methods
to kill the threads. Then call the default event handler for “OK” button
which will terminate the application.
................................
pMyThread1->KillThread();
pMyThread2->KillThread();
OnOK();
................................
Refer to the complete working sample of the User-Interface threads accompanied
with this article.
MTWorker Project
The important files of this project are MTWorkerDlg.h and MTWorkerDlg.cpp
which have the code for the dialog, GUI and the event handlers. They
also have the methods for the thread execution.
MTUserIntf Project
The important files of this project are MyThread.c , MyThread.cpp, MTUserIntfDlg.h
and MTUserIntfDlg.cpp. MTUserIntfDlg.h and MTUserIntfDlg.cpp has the code
for the dialog, GUI and the event handlers. MyThread.c and MyThread.cpp
implements the thread class. Dialog class will control the threads over their
lifetime.