Articles Guide to Choose and Implement a Worker Thread

emailx45

Местный
Регистрация
5 Май 2008
Сообщения
3,571
Реакции
2,438
Credits
573
Guide to Choose and Implement a Worker Thread
_Flaviu - 30/Jul/2020
[SHOWTOGROUPS=4,20,22]

A guide to choosing and implementing a worker thread in your application
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
A simple sample is listed below:
Hide Copy Code
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; // Could not create thread
}
return true;
}

private:
HANDLE m_hThread;

private:
static DWORD WINAPI ThreadFunction(LPVOID pvParam);
};
and thread function:
Hide Copy Code
DWORD CSample::ThreadFunction(LPVOID pvParam)
{
// do some useful things
}
Using:
Hide Copy Code
CSample sample;
if (! Create())
AfxMessageBox(_T("Could not create thread !"));
If your project is a MFC project, you better use Для просмотра ссылки Войди или Зарегистрируйся which is part of MFC.
Before you setup a thread method, you will have to take care of two things:
  1. a way to control the thread method from outside of your child thread
  2. 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:

// CMyDialog header
#define WMU_NOTIFYTHREAD (WM_APP + 12) // notification message
// from thread
#define THREADUPDATESTARTED 1
#define THREADUPDATESTOPPED 2
#define THREADUPDATEINFO 3

class CMyDialog : public CDialog
{
.....

protected:
static UINT WorkerThread(LPVOID lpParam); // this is the static method used
// inside of child thread

protected:
BOOL m_bThreadActive; // the variable is used to know
// the state of the child thread
volatile BOOL m_bRunThread; // the variable is used to control
// the child thread from outside
CWinThread* m_pWinThread; // CWinThread object used with AfxBeginThread

.....

// Generated message map functions
protected:
//{{AFX_MSG(CMyDialog)
afx_msg void OnDestroy();
afx_msg LRESULT OnNotifyThread(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
};
The implementation looks like this:
Hide Shrink
arrow-up-16.png
Copy Code
// CMyDialog implementation

BEGIN_MESSAGE_MAP(CMyDialog, CDialog)
//{{AFX_MSG_MAP(CMyDialog)
ON_WM_DESTROY()
//}}AFX_MSG_MAP
ON_MESSAGE(WMU_NOTIFYTHREADUPDATE, &CMyDialog::OnNotifyThreadUpdate)
END_MESSAGE_MAP()

CMyDialog::CMyDialog()
:m_pWinThread(NULL) // initialize all variables
, m_bRunThread(FALSE)
, m_bThreadActive(FALSE)
{
//
}

CMyDialog::StartThread()
{
// create thread as suspended
m_pWinThread = AfxBeginThread(&CMyDialog::WorkerThread, (LPVOID)this,
THREAD_PRIORITY_BELOW_NORMAL, CREATE_SUSPENDED, 0, NULL);
// setup additional information
m_pWinThread->m_bAutoDelete = TRUE; // this will tell the thread
// to auto clean at thread exit
m_bRunThread = TRUE;
m_pWinThread->ResumeThread(); // start the thread
}

CMyDialog::StopThread()
{
m_bRunThread = FALSE; // setup this flag as FALSE will stop the
// looping inside of thread and so the thread
}
and as WorkerThread method, you can have a kind of:
Hide Copy Code
UINT CMyDialog::WorkerThread(LPVOID lpParam)
{
CMyDialog* pDlg = (CMyDialog*)lpParam;
if (NULL == pDlg || NULL == pDlg->GetSafeHwnd())
return 1; // fail thread

::postMessage(pDlg->GetSafeHwnd(),
WMU_NOTIFYTHREAD, THREADUPDATESTARTED, 0); // notify main thread that child thread
// is started

BOOL bJobDone = FALSE;
while (pDlg->m_bRunThread || ! bJobDone) // Run loop inside thread
{
// do actual work
// if the job has been done, then set up bJobDone as TRUE
}

::postMessage(pDlg->GetSafeHwnd(),
WMU_NOTIFYTHREAD, THREADUPDATESTOPPED, 0); // notify main thread that child thread
// is stopped

return 0;
}
Also, we have to receive the notification from child thread:
Hide Copy Code
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:
Hide Copy Code
void CMyDialog::OnDestroy()
{
CDialog::OnDestroy();

// TODO: Add your message handler code here

if (NULL != m_pWinThread) // if thread is still active
{
m_bRunThread = FALSE; // close the looping inside thread
switch (WaitForSingleObject(m_pWinThread->m_hThread, 3000)) // wait for thread
// to close for 3 seconds
{
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:
Hide Shrink
arrow-up-16.png
Copy Code
UINT AFX_CDECL CMyDialog::WorkerThread(LPVOID lpParam)
{
CMyDialog* pDlg = (CMyDialog*)lpParam;
if (NULL == pDlg || NULL == pDlg->GetSafeHwnd())
return 1; // fail thread

::postMessage(pDlg->GetSafeHwnd(), WMU_NOTIFYTHREAD, THREADUPDATESTARTED, 0); // notify
// main thread that child thread is started

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: // arrHandle elem 0 (pDlg->m_EventChange.m_hObject)
// do something useful here
break;
case WAIT_OBJECT_0 + 1: // arrHandle elem 1 (pDlg->m_EventStop.m_hObject)
bContinue = FALSE;
break;
}
}

::postMessage(pDlg->GetSafeHwnd(),
WMU_NOTIFYTHREAD, THREADUPDATESTOPPED, 0); // notify main thread that
// child thread is stopped

return 0;
}


[/SHOWTOGROUPS]
 

emailx45

Местный
Регистрация
5 Май 2008
Сообщения
3,571
Реакции
2,438
Credits
573
[SHOWTOGROUPS=4,20,22]

Also, we have to receive the notification from child thread:
Hide Copy Code
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:
Hide Copy Code
void CMyDialog::OnDestroy()
{
CDialog::OnDestroy();

// TODO: Add your message handler code here

if (NULL != m_pWinThread) // if thread is still active
{
m_bRunThread = FALSE; // close the looping inside thread
switch (WaitForSingleObject(m_pWinThread->m_hThread, 3000)) // wait for thread
// to close for 3 seconds
{
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:
Hide Shrink
arrow-up-16.png
Copy Code
UINT AFX_CDECL CMyDialog::WorkerThread(LPVOID lpParam)
{
CMyDialog* pDlg = (CMyDialog*)lpParam;
if (NULL == pDlg || NULL == pDlg->GetSafeHwnd())
return 1; // fail thread

::postMessage(pDlg->GetSafeHwnd(), WMU_NOTIFYTHREAD, THREADUPDATESTARTED, 0); // notify
// main thread that child thread is started

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: // arrHandle elem 0 (pDlg->m_EventChange.m_hObject)
// do something useful here
break;
case WAIT_OBJECT_0 + 1: // arrHandle elem 1 (pDlg->m_EventStop.m_hObject)
bContinue = FALSE;
break;
}
}

::postMessage(pDlg->GetSafeHwnd(),
WMU_NOTIFYTHREAD, THREADUPDATESTOPPED, 0); // notify main thread that
// child thread is stopped

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:
Hide Copy Code
// CMyDialog header
class CMyDialog : public CDialog
{
....
....

protected:
// volatile BOOL m_bRunThread; // no need it anymore
CEvent m_EventStop;
CEvent m_EventChange;
CWinThread* m_pWinThread;
BOOL m_bThreadActive; // the variable is used to know the
// state of the child thread

....
}
In such a way, stopping thread code is modifying as follows:
Hide Copy Code
void CMyDialog::StopThread()
{
if (NULL != m_pWinThread) // if thread is still active
{
m_EventStop.SetEvent();
switch (WaitForSingleObject(&m_EventStop, 3000)) // wait for thread
// to close for 3 seconds
{
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:
Hide Copy Code
void CMyDialog::RunThreadJob()
{
m_EventChange.SetEvent(); // this will start the inside thread job
}
The starting thread method will be slightly different:
Hide Copy Code
CMyDialog::StartThread()
{
m_EventStop.ResetEvent();
m_EventChange.ResetEvent();
// create thread as suspended
m_pWinThread = AfxBeginThread(&CMyDialog::WorkerThread,
(LPVOID)this, THREAD_PRIORITY_BELOW_NORMAL, CREATE_SUSPENDED, 0, NULL);
// setup additional information
m_pWinThread->m_bAutoDelete = TRUE; // this will tell the thread
// to auto clean at thread exit
m_pWinThread->ResumeThread(); // start the thread
}
Even so, we still have to wait for the thread to be stopped at our app(dialog) exit:
Hide Copy Code
void CMyDialog::OnDestroy()
{
CDialog::OnDestroy();

// TODO: Add your message handler code here

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:
Hide Copy Code
// CMyDialog header

CMyDialog::CMyDialog()
{
....

private:
// BOOL m_bThreadActive; // in this case, we don't need
// this anymore, we know if there
// is any thread active by
// checking m_arrThreads.GetSize()
volatile BOOL m_bRunThread; // the variable is used to control
// the child thread from outside
CTypedPtrArray<CObArray, CWinThread*> m_arrThreads; // array of CWinThread objects
....
}
The static working thread method could look like this:
Hide Copy Code
UINT CMyDialog::WorkerThread(LPVOID lpParam)
{
CMyDialog* pDlg = (CMyDialog*)lpParam;
if (NULL == pDlg || NULL == pDlg->GetSafeHwnd())
return 1; // fail thread

while (pDlg->m_bRunThread) // Run loop inside thread
{
::postMessage(pDlg->GetSafeHwnd(), WMU_NOTIFYTHREAD, THREADUPDATEINFO,
(LPARAM)::GetCurrentThreadId()));
// do hard work job
}

return 0;
}
We receive in the main thread the child thread notifications:
Hide Copy Code
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:
Hide Copy Code
void CMyDialog::StartThread()
{
CWinThread* pThread = AfxBeginThread(&CMyDialog::WorkerThread, (LPVOID)this,
THREAD_PRIORITY_BELOW_NORMAL, 0, 0, NULL);
m_arrThreads.Add(pThread); // add new created thread to m_arrThreads array
}
Every time we call this method, we create a new thread, and when you want to stop all threads can do:
Hide Copy Code
void CMyDialog::StopThreads()
{
const int nSize = m_arrThreads.GetSize();
if (nSize > 0)
{
m_bRunThread = FALSE; // stop looping inside of thread method
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 Для просмотра ссылки Войди или Зарегистрируйся or Для просмотра ссылки Войди или Зарегистрируйся.

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 Для просмотра ссылки Войди или Зарегистрируйся as well. However, if the situation is requesting, you can synchronize m_bRunThread with InterlockExchange just like this: change m_bRunThread variable as static:
Hide Copy Code
// CMyDialog header

class CMyDialog : public CDialog
{

......

static BOOL m_bRunThread;

}
and:
Hide Copy Code
// CMyDialog implementation

BOOL CMyDialog::m_bRunThread = FALSE;
and every time when you need to change its value, you write:
Hide Copy Code
::InterlockedExchange((LONG)&m_bRunThread, 0);

// or

::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).

Points of Interest
I hope this article will be useful for those who want to choose and implement a thread for the first time.


[/SHOWTOGROUPS]