This article intends to help programmers to choose and implement a worker thread in a Win32 / MFC application.
Introduction
First, there are several thread functions: _beginthread()
, CreateThread
, AfxBeginThread
. What should I choose? To decide, we have to enumerate them.
Using the Code
_beginthread
- Besides the sister _beginthreadex
function, is part of C run - time library
In this link, you can find a small example of how to use them in a real situation. CreateThread
- This is a Win32 API function.
A simple sample is listed below:
class CSample
{
public:
CSample() { m_hThread = 0; }
~CSample() { CloseHandle(m_hThread); }
bool Create()
{
m_hThread = CreateThread(0, 0, ThreadFunction, this, 0, 0);
if(! m_hThread)
{
return false; }
return true;
}
private:
HANDLE m_hThread;
private:
static DWORD WINAPI ThreadFunction(LPVOID pvParam);
};
and thread function:
DWORD CSample::ThreadFunction(LPVOID pvParam)
{
}
Using:
CSample sample;
if (! Create())
AfxMessageBox(_T("Could not create thread !"));
If your project is a MFC project, you better use AfxBeginThread function which is part of MFC.
Before you setup a thread method, you will have to take care of two things:
- a way to control the thread method from outside of your child thread
- a way to know the main thread what is the state of your child thread
And you need a callback function or a static
method in order to run it inside of your thread. Depending on what you need to do inside your thread, the implementation could be little different. Suppose you have to do a long time looping code, let's say you need to download something from the internet. This could take time. Here is the code for this kind of implementation:
#define WMU_NOTIFYTHREAD (WM_APP + 12) // notification message
#define THREADUPDATESTARTED 1
#define THREADUPDATESTOPPED 2
#define THREADUPDATEINFO 3
class CMyDialog : public CDialog
{
.....
protected:
static UINT WorkerThread(LPVOID lpParam);
protected:
BOOL m_bThreadActive; volatile BOOL m_bRunThread; CWinThread* m_pWinThread;
.....
protected:
afx_msg void OnDestroy();
afx_msg LRESULT OnNotifyThread(WPARAM wParam, LPARAM lParam);
};
The implementation looks like this:
BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
ON_WM_DESTROY()
ON_MESSAGE(WMU_NOTIFYTHREADUPDATE, &CMyDialog::OnNotifyThread)
END_MESSAGE_MAP()
CMyDialog::CMyDialog()
:m_pWinThread(NULL) , m_bRunThread(FALSE)
, m_bThreadActive(FALSE)
{
}
CMyDialog::StartThread()
{
m_pWinThread = AfxBeginThread(&CMyDialog::WorkerThread, (LPVOID)this,
THREAD_PRIORITY_BELOW_NORMAL, CREATE_SUSPENDED, 0, NULL);
m_pWinThread->m_bAutoDelete = TRUE; m_bRunThread = TRUE;
m_pWinThread->ResumeThread(); }
CMyDialog::StopThread()
{
m_bRunThread = FALSE; }
and as WorkerThread
method, you can have a kind of:
UINT CMyDialog::WorkerThread(LPVOID lpParam)
{
CMyDialog* pDlg = (CMyDialog*)lpParam;
if (NULL == pDlg || NULL == pDlg->GetSafeHwnd())
return 1;
::PostMessage(pDlg->GetSafeHwnd(),
WMU_NOTIFYTHREAD, THREADUPDATESTARTED, 0);
BOOL bJobDone = FALSE;
while (pDlg->m_bRunThread && ! bJobDone) {
}
::PostMessage(pDlg->GetSafeHwnd(),
WMU_NOTIFYTHREAD, THREADUPDATESTOPPED, 0);
return 0;
}
Also, we have to receive the notification from child thread:
LRESULT CMyDialog::OnNotifyThread(WPARAM wParam, LPARAM lParam)
{
switch (wParam)
{
case THREADUPDATESTARTED:
m_bThreadActive = TRUE;
break;
case THREADUPDATESTOPPED:
m_bRunThread = FALSE;
m_bThreadActive = FALSE;
break;
}
return 1;
}
You also have to take care not to close your app(dialog) until you are sure that child thread is stopped:
void CMyDialog::OnDestroy()
{
CDialog::OnDestroy();
if (NULL != m_pWinThread) {
m_bRunThread = FALSE; switch (WaitForSingleObject(m_pWinThread->m_hThread, 3000)) {
case WAIT_OBJECT_0:
TRACE(_T("The worker thread just finished\n"));
break;
case WAIT_TIMEOUT:
TRACE(_T("Cannot stop gracefully the worker thread\n"));
break;
}
}
}
There is another situation inside thread method: you can have there a waitable function. Here is a sample:
UINT AFX_CDECL CMyDialog::WorkerThread(LPVOID lpParam)
{
CMyDialog* pDlg = (CMyDialog*)lpParam;
if (NULL == pDlg || NULL == pDlg->GetSafeHwnd())
return 1;
::PostMessage(pDlg->GetSafeHwnd(), WMU_NOTIFYTHREAD, THREADUPDATESTARTED, 0);
CArray<HANDLE, HANDLE&> arrHandle;
arrHandle.Add(pDlg->m_EventChange.m_hObject);
arrHandle.Add(pDlg->m_EventStop.m_hObject);
BOOL bContinue = TRUE;
while (bContinue)
{
const DWORD dwResult = WaitForMultipleObjects(arrHandle.GetSize(),
arrHandle.GetData(), FALSE, INFINITE);
switch (dwResult)
{
case WAIT_OBJECT_0: break;
case WAIT_OBJECT_0 + 1: bContinue = FALSE;
break;
}
}
::PostMessage(pDlg->GetSafeHwnd(),
WMU_NOTIFYTHREAD, THREADUPDATESTOPPED, 0);
return 0;
}
As you see the code, some changes must be made in this case. First, the child thread is stopped by a CEvent
, and is controlled by a CEvent
. In this way, we get rid of m_bRunThread
variable. Instead, we have to add other two, CEvent
type:
class CMyDialog : public CDialog
{
....
....
protected:
CEvent m_EventStop;
CEvent m_EventChange;
CWinThread* m_pWinThread;
BOOL m_bThreadActive;
....
}
In such a way, stopping thread code is modifying as follows:
void CMyDialog::StopThread()
{
if (NULL != m_pWinThread) {
m_EventStop.SetEvent();
switch (WaitForSingleObject(&m_EventStop, 3000)) {
case WAIT_OBJECT_0:
TRACE(_T("The worker thread just finished\n"));
break;
case WAIT_TIMEOUT:
TRACE(_T("Cannot stop gracefully the worker thread\n"));
break;
}
}
}
To run the job inside of your child thread will be done by:
void CMyDialog::RunThreadJob()
{
m_EventChange.SetEvent(); }
The starting thread method will be slightly different:
CMyDialog::StartThread()
{
m_EventStop.ResetEvent();
m_EventChange.ResetEvent();
m_pWinThread = AfxBeginThread(&CMyDialog::WorkerThread,
(LPVOID)this, THREAD_PRIORITY_BELOW_NORMAL, CREATE_SUSPENDED, 0, NULL);
m_pWinThread->m_bAutoDelete = TRUE; m_pWinThread->ResumeThread(); }
Even so, we still have to wait for the thread to be stopped at our app(dialog) exit:
void CMyDialog::OnDestroy()
{
CDialog::OnDestroy();
StopThread();
}
What if we need multiple threads ? We still have to control the threads and notify the main dialog regarding the state of every thread. To handle all these, we have to modify the header accordingly:
CMyDialog::CMyDialog()
{
....
private:
volatile BOOL m_bRunThread; CTypedPtrArray<CObArray, CWinThread*> m_arrThreads; ....
}
The static
working thread method could look like this:
UINT CMyDialog::WorkerThread(LPVOID lpParam)
{
CMyDialog* pDlg = (CMyDialog*)lpParam;
if (NULL == pDlg || NULL == pDlg->GetSafeHwnd())
return 1;
while (pDlg->m_bRunThread) {
::PostMessage(pDlg->GetSafeHwnd(), WMU_NOTIFYTHREAD, THREADUPDATEINFO,
(LPARAM)::GetCurrentThreadId()));
}
return 0;
}
We receive in the main thread the child thread notifications:
LRESULT CMyDialog::OnNotifyThread(WPARAM wParam, LPARAM lParam)
{
switch (wParam)
{
case THREADUPDATEINFO:
TRACE("Get message from thread with id: %d\n", lParam);
break;
}
return 1;
}
The starting thread method should look like this:
void CMyDialog::StartThread()
{
CWinThread* pThread = AfxBeginThread(&CMyDialog::WorkerThread, (LPVOID)this,
THREAD_PRIORITY_BELOW_NORMAL, 0, 0, NULL);
m_arrThreads.Add(pThread); }
Every time we call this method, we create a new thread, and when you want to stop all threads can do:
void CMyDialog::StopThreads()
{
const int nSize = m_arrThreads.GetSize();
if (nSize > 0)
{
m_bRunThread = FALSE; HANDLE* pHandles = new HANDLE[nSize];
for (int nIndex = 0; nIndex < nSize; ++nIndex)
pHandles[nIndex] = m_arrThreads[nIndex]->m_hThread;
::WaitForMultipleObjects(nSize, pHandles, TRUE, INFINITE);
m_arrThreads.RemoveAll();
delete[] pHandles;
}
}
When you process and access data from more than main thread is involved in the synchronization. In simple words, the syncronization means that you must take care to access a resource only once at a time.
If the shared resource is hosted in the main thread, then you can do syncronization from one or from multiple threads by simply using PostMessage
from your child thread(s), which I did in every exposed example. The syncronization is done by using message table. If the shared resource must be accessed by concurrency between threads, you should use Mutex or Critical Section Objects.
Fortunately, this is not such a common situation. A good practice is to use mutex or critical sections only if you really need it. And InterlockExchange as well. However, if the situation is requesting, you can synchronize m_bRunThread
with InterlockExchange
just like this: change m_bRunThread
variable as static
:
class CMyDialog : public CDialog
{
......
static BOOL m_bRunThread;
}
and:
BOOL CMyDialog::m_bRunThread = FALSE;
and every time when you need to change its value, you write:
::InterlockedExchange((LONG)&m_bRunThread, 0);
::InterlockedExchange((LONG)&m_bRunThread, 1);
Until now, I exemplified just simple thread routines. If you need to do complicated things inside of your thread method, then the code will be longer and this must signal that you have a design issue. In this case, you will have to derive your own CThread
class where you can handle whatever you need. In this CThread
derived class, you can implement other features for your thread (pause thread, or other sophisticated syncronization methods).
You can find here (on www.codeproject.com) a lot of CThread
derived classes for this purpose. This approach is not the goal of this article.
Points of Interest
I hope this article will be useful for those who want to choose and implement a thread for the first time.
History
- 30th July, 2020 - First release