[ Home | Syllabus | Course Notes | Assignments | Search]
There are two kinds of threads:
Threads are common today but in the past other mechanisms were used to provide for background/foreground computation including:
The three example programs in Chapter 12 demonstrate the basics of threaded programming in MFC.
All three programs solve the same problem.
PROBLEM: how to update display and do the computation at the same time.
// ComputeDlg.cpp : implementation file
/////////////////////////////////////////////////////////////////////////////
// CComputeDlg dialog
CComputeDlg::CComputeDlg(CWnd* pParent /*=NULL*/)
: CDialog(CComputeDlg::IDD, pParent)
{
m_nCount = 0; // used to track computation
}
BEGIN_MESSAGE_MAP(CComputeDlg, CDialog)
//{{AFX_MSG_MAP(CComputeDlg)
ON_WM_TIMER() // timer to trigger update of display
ON_BN_CLICKED(IDC_START, OnStart) // button to start computation
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CComputeDlg message handlers
void CComputeDlg::OnTimer(UINT nIDEvent)
{
CProgressCtrl* pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS1);
pBar->SetPos(m_nCount * 100 / nMaxCount); // show percent complete
}
void CComputeDlg::OnStart()
{
MSG message;
m_nTimer = SetTimer(1, 100, NULL); // 1/10 second
ASSERT(m_nTimer != 0); // check for failure
GetDlgItem(IDC_START)->EnableWindow(FALSE);
//turn off button while computing
volatile int nTemp; // volative says don't keep in register - will be shared variable later on
for (m_nCount = 0; m_nCount < nMaxCount; m_nCount++) {
for (nTemp = 0; nTemp < 10000; nTemp++) { // do some computation
// uses up CPU cycles
}
if (::PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
// periodically (every 10000 cycles) check for messages
// in particular the Timer message
// Notice this is the message pump we say earlier in WinMain loop!
::TranslateMessage(&message);
::DispatchMessage(&message);
}
}
CDialog::OnOK(); // quit dialog when done
}
void CComputeDlg::OnCancel()
{
if (m_nCount == 0) { // prior to Start button
CDialog::OnCancel();
}
else { // computation in progress
m_nCount = nMaxCount; // Force exit from OnStart
}
}
This example puts the computation in its own thread so that it does not have to poll the message queue.
int g_nCount = 0; // global variable
UINT ComputeThreadProc(LPVOID pParam)
// this function will become a thread
// thread functions take one parameter which is a pointer
// to anything the parent thread wants to send this thread function
{
volatile int nTemp; // volatile else compiler optimizes too much
for (g_nCount = 0; g_nCount < CComputeDlg::nMaxCount;
::InterlockedIncrement((long*) &g_nCount)) {
// Does an atomic increment so don't need critical section
for (nTemp = 0; nTemp < 10000; nTemp++) {
// uses up CPU cycles
}
}
// WM_THREADFINISHED is user-defined message
::PostMessage((HWND) pParam, WM_THREADFINISHED, 0, 0);
// "::" means use the non MFC function
g_nCount = 0;
return 0; // ends the thread
}
BEGIN_MESSAGE_MAP(CComputeDlg, CDialog)
ON_MESSAGE(WM_THREADFINISHED, OnThreadFinished)
// use message to signal when done (since main thread has a message pump loop
//{{AFX_MSG_MAP(CComputeDlg)
ON_BN_CLICKED(IDC_START, OnStart)
ON_WM_TIMER()
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CComputeDlg message handlers
void CComputeDlg::OnStart()
{
m_nTimer = SetTimer(1, 100, NULL); // 1/10 second
ASSERT(m_nTimer != 0);
GetDlgItem(IDC_START)->EnableWindow(FALSE);
AfxBeginThread(ComputeThreadProc, GetSafeHwnd(),
THREAD_PRIORITY_NORMAL);
// GetSafeHwnd - gives handle to parent window so can send it a message
// this becomes the argument to the thread function
}
void CComputeDlg::OnCancel()
{
if (g_nCount == 0) { // prior to Start button
CDialog::OnCancel();
}
else { // computation in progress
g_nCount = nMaxCount; // Force thread to exit
}
}
void CComputeDlg::OnTimer(UINT nIDEvent)
{
CProgressCtrl* pBar = (CProgressCtrl*) GetDlgItem(IDC_PROGRESS1);
pBar->SetPos(g_nCount * 100 / nMaxCount);
}
LRESULT CComputeDlg::OnThreadFinished(WPARAM wParam, LPARAM lParam)
{
CDialog::OnOK(); // close dialog
return 0;
}
// ComputeDlg.cpp : implementation file
volatile int g_nCount;
CEvent g_eventStart; // creates autoreset events
CEvent g_eventKill;
UINT ComputeThreadProc(LPVOID pParam)
{
volatile int nTemp;
::WaitForSingleObject(g_eventStart, INFINITE);
// wait to start
for (g_nCount = 0; g_nCount < CComputeDlg::nMaxCount;
g_nCount++) {
for (nTemp = 0; nTemp < 10000; nTemp++) {
// Simulate computation
}
if (::WaitForSingleObject(g_eventKill, 0) == WAIT_OBJECT_0) {
// check to see if we should stop (not as efficient as previous program)
break;
}
}
// Tell owner window we're finished
::PostMessage((HWND) pParam, WM_THREADFINISHED, 0, 0);
g_nCount = 0;
return 0; // ends the thread
}
/////////////////////////////////////////////////////////////////////////////
// CComputeDlg message handlers
BOOL CComputeDlg::OnInitDialog()
{
CDialog::OnInitDialog();
AfxBeginThread(ComputeThreadProc, GetSafeHwnd());
// You can start the thread here since it is controlled by Events
return TRUE; // return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
void CComputeDlg::OnStart()
{
m_nTimer = SetTimer(1, 100, NULL); // 1/10 second
ASSERT(m_nTimer != 0);
GetDlgItem(IDC_START)->EnableWindow(FALSE);
g_eventStart.SetEvent();
// wake up the thread
}
void CComputeDlg::OnCancel()
{
if (g_nCount == 0) { // prior to Start button
// must start it before we can kill it
g_eventStart.SetEvent();
}
g_eventKill.SetEvent();
// send kill event
}