Windows NT Systems Programming: Spring 2000

[ Home | Syllabus | Course Notes | Assignments | Search]


Threads in MFC programs (Chapter 12)


There are two kinds of threads:

  1. Worker threads: communicate with global variables and synchronization events
  2. User Interface Threads: these have a message loop (aka message pump) and can thus recieve messages.
    Must have a window (possibly hidden) to which the message can be sent.
    A modeless dialog box must be controlled by a user interface thread.

Threads are common today but in the past other mechanisms were used to provide for background/foreground computation including:

  1. polling (to see if the user wants to do anything
  2. OnIdle - computer when there is nothing else going on.

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.


Single Thread with  Message Polling

 

(source code here)
// 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
    }
}

Computation in Worker Thread

This example puts the computation in its own thread so that it does not have to poll the message queue.

(source code here)

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;
}

Threads and Synchronization

(source code here)

// 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
}



Copyright chris wild 1999/2000.
For problems or questions regarding this web contact [Dr. Wild].
Last updated: February 23, 2000.