Introduction
Have you noticed how often we ignore object-oriented concepts when doing even very simple multithreading? There are two typical examples:
- You code a pure Win32 project in C++, and you just don't have time to devise some object-oriented thread API wrapper, so you simply create thread procedures here and there, eventually making the code unreadable.
- You code an MFC application, and you need a worker thread. What do you do? Sure thing, you call
AfxBeginThread
, and (again) pass in a pointer to a thread procedure, ruining the object-oriented nature of the application.
Threads are fairly independent entities in an application, and as such, they must be separated from other implementations. The place where a thread procedure belongs least of all is a CDialog
-derived class.
That's why I decided to make a thin wrapper class for a basic thread API. I use this class regularly, and I think it makes my applications better.
Using the Code
The code is actually a small class, Thread
. Each instance of this class represents a thread, and the class contains some basic functions to control the thread, including gracefully (or ungracefully, if need be) terminating it.
Let's go through the steps necessary to set up a thread with this class:
- Derive a class from
Thread
. It will be the class that you later instantiate and use. - Override the virtual function called
ThreadProc
. The implementation in your derived class is the substitution for a usual thread procedure. This virtual function must follow some basic rules to work properly. These are:
- Never call _
endthread
, _endthreadex
, ExitThread
, and such from within ThreadProc
. Instead, simply return
. - From time to time (as often as possible), call
GetStop
to immediately clean up and return if GetStop
returns true
. See the example below.
- Instantiate
Thread
. Make sure you don't create it on the stack because the lifetime of the Thread
object must be at least as long as that of the thread itself. A good place for a Thread
-derived object is among members in some CWnd
-derived class. - Use the
Thread::Start
member function to start the thread. That's it! - Optionally, you can control the thread from anywhere (including the thread procedure and your main thread) by calling its member functions.
Example of a good Thread
-derived class:
class MyThread : public Thread
{
protected:
virtual unsigned int ThreadProc()
{
void *pData = GetData();
for(int i=0; i<1000; ++i)
{
if(GetStop())
return 0;
Sleep(100);
}
return 0;
}
};
Some window class in your application could then have a member declared as follows:
protected:
MyThread thread;
And finally, some member function of this class could call:
thread.Start(this);
See the demo application for more details.
Points of Interest
Note that the thread function is a non-static member of the Thread
class. This is something you can't normally do because of the this
pointer implicitly passed to non-static member functions. That's why ThreadProc
gets called not directly, but through a mediator, _ThreadProc
, which is a static class member. The rest of the implementation is pretty straightforward, and deserves no special explanation.
Disclaimer
Use this code at your own risk. Although I'm pretty sure it's safe, I still had to say this :D
Happy coding!
Revision History
- Jan 31, 2007 - Modified the demo application to eliminate
SendMessage
calls across threads. - Jan 30, 2007 - Originally posted.