(Introduction to how to derive class from CWinThread safely)
It is really helpful to learn multithread programming in MFC application development. Enjoy it!
cylee
2011.7.15
Introduction
My recent forray into threads and multithreaded applications has taught me a few lessons which I thought I'd pass on. As I suspect that most people know this already (it was just me taking a while to catch up), the target audience is beginners to the world of multithreading. If you're not a beginner, then please read this anyway and let me know if I've got it all wrong!
The Problem
My problem was that I wanted several threads to basically do the same thing.
Being the OO developer that I am I wanted to be able to wrap this up in a class
and just create several instances of that class and Bob's your uncle.
Unfortunately, the function that you pass to AfxBeginThread
must be either
declared outside a class or be a static class member function. Being OO-type
people who want to try to reduce namespace pollution we'll be using
the static class member function approach, unfortunately this means that
all of our class instances will be using the same function, which is exactly
what I'm trying to avoid.
Racing for the Handle
By way of an introduction I'll start off with a brief description of what
AfxBeginThread
does. Essentially AfxBeginThread
is a helper function that
handles setting up and starting a CWinThread
object for us. To do this it goes
through (broadly speaking) four steps:
-
Allocate a new
CWinThread
object on the heap. -
Call
CWinThread::CreateThread()
setting it to initially start suspended. -
Set the thread's priority.
-
Call
CWinThread::ResumeThread()
.
The default usage of CWinThread
and AfxBeginThread
has a slight problem.
By default, as soon as the thread has completed it is deleted and so if our
thread doesn't take much time to complete, by the time that AfxBeginThread
returns
our CWinThread
pointer may already have been deleted and so dereferencing
this pointer to get at the thread handle for a WaitForSingleObject()
call will
cause our application to crash.
Fortunately CWinThread
provides a simple answer to this by way of the
m_bAutoDelete
member variable. As described above, this is TRUE
by default.
However, if we set it to FALSE
, then the thread will not delete itself when it
completes. This does have the same handle racing problem described above,
because we can't access the member until AfxBeginThread
returns,
by which time it could already have been deleted! The answer is not to use the
default AfxBeginThread
and to create the thread initially suspended:
1 CWinThread* pThread = AfxBeginThread (ThreadFunc, lpVoid, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
2 ASSERT (NULL != pThread);
3 pThread->m_bAutoDelete = FALSE;
4 pThread->ResumeThread ();
Obviously, this does entail a little extra handling ourselves, but we're big boys and girls now aren't we!
The Solution
The solution to our problem isn't too complex and doesn't require too much
work. Essentially, we are going to derive a class from CWinThread
with a static
member thread function, but this thread function will call a non-static member
function to do the main guts of what our thread is supposed to do. It will also
set the m_bAutoDelete
member to FALSE
in the constructor to overcome the handle racing
problem.
Basically our class will look like this:
1 class CThreadEx : public CWinThread {
2 public:
3 CMyThread (AFX_THREADPROC pfnThreadProc);
4 static UINT ThreadFunc (LPVOID lpParam);
5 protected:
6 virtual UINT Execute ();
7 };
8
9 CThreadEx::CThreadEx (AFX_THREADPROC pfnThreadProc) : CWinThread (pfnThreadProc, NULL) {
10 m_bAutoDelete = FALSE;
11
12 // Undocumented variable. Need to set the thread parameters variable to this
13
14 m_pThreadParams = this;
15 }
16
17 // Static
18
19 UINT CThreadEx::ThreadFunc (LPVOID lpParam) {
20 ASSERT (NULL != lpParam);
21 CThreadEx* pThread = (CThreadEx*)lpParam;
22 return pThread->Execute ();
23 }
24
25 UINT CThreadEx::Execute () {
26 // Do something useful ...
27
28 // Return a value to indicate success/failure
29
30 return 0;
31 }
Those of you with a keen eye will have noticed that the
constructor for CThreadEx
uses an initialiser list to call a CWinThread
constructor that isn't documented. If you look in "thrdcore.cpp", you'll find a
constructor that looks like this:
1 CWinThread::CWinThread(AFX_THREADPROC pfnThreadProc, LPVOID pParam)
2 {
3 m_pfnThreadProc = pfnThreadProc;
4 m_pThreadParams = pParam;
5
6 CommonConstruct();
7 }
This introduces us to two undocumented features, firstly
the constructor itself, and secondly the member variables m_pfnThreadProc
and
m_pfnThreadParams
. Also in "thrdcore.cpp" is the source for AfxBeginThread
itself. The line below shows the use of the above constructor:
1 CWinThread* pThread = DEBUG_NEW CWinThread(pfnThreadProc, pParam);
m_pThreadParams = this
.
Implementation
You have two options when it comes to implementing this solution. If you only require a one off solution, then use the above class as a template and build your functionality into it. If you think that this is something you'll be doing quite a lot, then define the above class as your base class and inherit from it putting your functionality in the derived class.
Using it all looks something like this:
CThreadEx* pThread = new CThreadEx (CThreadEx::ThreadFunc);
VERIFY (pThread->CreateThread());
...
WaitForSingleObject (pThread->m_hThread, INFINITE);
delete pThread;
The thread can be initially started suspended by
specifying CREATE_SUSPENDED
as the first parameter to the CreateThread()
call if
this functionality is required. Also, if you need to use a thread priority other
than THREAD_PRIORITY_NORMAL
just call SetThreadPriority()
after
CreateThread()
.
Conclusion
And there you have it ... a thread class that allows multiple instances to manipulate their own member data, etc. without having to have a separate thread function for each one. I hope this helps someone out there, it certainly helped me.
License
This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.
A list of licenses authors might use can be found here