Concurrency and Synchronization, страница 3

·  How do we define and handle a user-defined message?

We put the line


in some header file that is included in all the files that mention the new message.  For example,  in this program, I put it in ThreadTest.h.    The identifier  WM_USER marks the place where the Win32 API says you can start defining your own messages.   But MFC uses a few, so you are advised to start with WM_USER+5 when writing MFC programs.

Don't put this line in resource.h,  since  Visual Studio is always regenerating that file, and will wipe out your new line.

Now to get the message mapped to the right message handler.   You cannot do this with Class Wizard, which cannot handle user-defined messages.    Here are the steps:

In ComputeDlg.cpp,   after  BEGIN_MESSAGE_MAP  but outside the AFX_MSG_MAP brackets:



It goes all on one line, though it won't fit on one line here.

In  ComputeDlg.h, add the prototype of the handler:

afx_msg LRESULT OnThreadFinished(WPARAM wParam, LPARAM lParam);

Then, in ComputeDlg.cpp,  add the message handler itself.

LRESULT CComputeDlg::OnThreadFinished(WPARAM wParam, LPARAM lParam)

{ GetDlgItem(ID_START)->EnableWindow(TRUE);

KillTimer(m_nTimer);  // otherwise there’s a memory leak

return 0;


This leaves the dialog up;  we removed the OK button.  Of course, we could call CDialog::OnOK()  to dismiss it, but instead we just leave it up,  enabling the Start button again.

Here it is in context: Italics show the added code.

In ComputeDlg.cpp:









In ComputeDlg.h:

class CComputeDlg : public CDialog


// Construction


int m_nCount;

CComputeDlg(CWnd* pParent = NULL);  

    afx_msg LRESULT OnThreadFinished(WPARAM wParam,           LPARAM lParam);

// Dialog Data


That finishes the explanation of user-defined messages.

·  Why use the view window's handle instead of a pointer to the view window as a CWnd object?

Because the classes derived from CObject are not "thread-safe". 

The details of exactly what this means are complicated, and as a normal Windows programmer, you do not need to know them.  You just need to know that you should not pass pointers to objects derived from CWnd  from one thread to another.  It is all right to pass a pointer to CRect or CString, for example,  but not to a CWnd, or for example to a CButton, since CButton is derived from CWnd.  

7.   Now we've programmed the worker thread.  Here's how to stop it:

void CComputeDlg::OnCancel()

{ if(g_nCount == 0)  // prior to Start button


else  // computation in progress

g_nCount = g_nMaxCount;  // force exit


8.  It only remains to start the thread. Here's the way Kruglinksi does it.   Below I discuss another way, which is often better.

void CComputeDlg::OnStart()


m_nTimer = SetTimer(1,100,NULL);


AfxBeginThread(ComputeThreadProc, GetSafeHwnd(), THREAD_PRIORITY_NORMAL);  


Well, the program works.   You can press Start, and then interrupt the thread by pressing Cancel.   You can tell it worked, because the progress bar stops and the Start button is re-enabled.   Or, if you don't interrupt, the thread stops when it is done, and the Start button is re-enabled.

Creating Worker Threads

Method 1  (as in the Kruglinksi example)

Supply a pointer to a global “thread procedure” that will be used in place of Run.   This pointer is used as a parameter to the MFC function Afx_BeginThread.   Although this works OK in the example given in this lecture, it is harder to work with in more realistic examples.

Method 2

The default Run in CWinThread has a message loop, so to create a worker thread, you first create a class derived from CWinThread, and then override Run.