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