线程和线程同步化 详解!

时间:2022-10-31 23:36:42

   在Microsoft Win32环境中,每个运行的应用程序都构成一个“进程”,而每个“进程”都包含一个或多个“执行线程”。线程指的是程序代码的执行途径,外加一组操作系统分配的资源(堆栈、寄存器状态等等)。

      程序是计算机指令的集合,它以文件的形式存储在磁盘上。而进程通常被定义为一个 正在运行 的程序的实例,是一个程序在在自身的地址空间中的一次执行活动。而进程的执行是靠线程来实现的。一个程序可以对应多个进程,例如:可以同时打开多个“记事本”程序的进程。进程是资源申请、调度和运行的独立单位。因此它使用系统中的运行资源,而程序不能申请系统资源,不能被系统调度,也不能作为独立运行的单位,因此,它不占用系统的运行资源。

      16位和32位Microsoft Windows版本的根本区别 就在于 32位Windows没有“每个应用程序只允许有一个线程”的限制。32位Windows应用程序的进程是从单个线程开始的,但是线程还可以繁衍另外的线程。操作系统内核中的 调度器 在活动的线程间分配CPU时间,使他们看上去像是在同时运行。对于既要在前端处理用户输入,同时又要执行后台任务的工作,线程是一种理想的选择。它们也可起到可见的作用,像“主线程处理 发送给应用程序主窗口 的消息”那样,线程也可以创建窗口并处理发送给这些窗口的消息。

      MFC在CWinThread类中封装了可执行线程,它还在易于使用的c++类中封装了事件、互斥、和其它win32线程同步对象,在MFC中编写多线程程序的关键是1、确实知道自己想要做什么。2、麻烦会出在哪儿。

线程概述

      对Windows来说,所有线程都是一样的。但是MFC却把线程区分为两种类型:User Interface thread(用户界面UI线程)和Worker thread(工作者线程)。两类线程的不同之处在于:UI线程具有消息循 环而工作者线程没有。UI线程可以创建窗口并处理发送给这些窗口的消息,工作者线程处理后台任务,因其不接收用户的直接输入,所以不需要窗口和消息循环。

      UI线程最常见的用法是创建多个由各自的 执行线程 服务的窗口。而工作者线程非常适合于执行那些可以从应用程序的其它部分中分离的独立任务以及在后台中执行的任务。

1.1创建工作线程

      在MFC应用程序中启动一个线程的最后方法是调用AfxBeginThread。MFC定义了2个不同的AfxBeginThread版本:一个启动UI线程,另一个启动工作者线程。在MFC程序中,只有在“线程不使用MFC时”才可使用win32::CreateThread函数来创建线程。AfxBeginThread不仅仅是对win32::CreateThread函数的封装;除了启动线程以外,它还要初始化主结构使用的“内部状态信息”,在线程创建过程中的不同地方执行合理的检查,并采用一定的方法来确保以“线程安全”的方式访问“C运行时库”中的函数。

     AfxBeginThread使创建工作者线程变得非常简单,几乎微不足道。当调用AfxBeginThread时,它将创建一个新的CWinThread对象,启动一个线程并使线程附属于CWinThread对象,返回一个CWinThread指针。语句:

CWinThread* pThread=AfxBeginThread(ThreadFunc,&threadInfo);启动一个工作者线程,并给它传递一个应用程序定义的数据结构的地址(&threadInfo),其中包含了对线程的输入。ThreadFunc是“线程函数”,这类函数在线程开始执行后才得以执行。AfxBeginThread函数原型如下,参数详解:

CWinThread* AfxBeginThread( AFX_THREADPROC pfnThreadProc,

                                         LPVOID pParam,

                                         int nPriority = THREAD_PRIORITY_NORMAL,

                                         UINT nStackSize = 0,

                                         DWORD dwCreateFlags = 0,

                                         LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL

                                        );//用于创建工作者线程

返回值: 一个指向新线程的线程对象的指针

pfnThreadProc : 线程的入口函数,该函数为回调函数,因此它必须是静态成员函数或是在类外部声明的全局函数,其原型如下:

                       UINT MyThreadFunction( LPVOID pParam );其参数为传递给AfxBeginThread函数的pParam参数。

pParam : 传递入线程的参数,注意它的类型为:LPVOID,所以我们可以传递一个结构体入线程.该结构体包含了工作者线程的 创建线程

             传递给工作者线程的信息。

nPriority : 线程的优先级,一般设置为 0 .让它和主线程具有共同的优先级.

nStackSize : 指定新创建的线程的栈的大小.如果为 0,新创建的线程具有和主线程一样的大小的栈

dwCreateFlags : 指定创建线程以后,线程有怎么样的标志.可以指定两个值:

                   CREATE_SUSPENDED : 线程创建以后,会处于挂起状态,直到调用:ResumeThread

                      0 : 创建线程后就开始运行.

lpSecurityAttrs : 指向一个 SECURITY_ATTRIBUTES 的结构体,用它来标志新创建线程的安全性.如果为 NULL , 那么新创建的线程就具有和主线程一样的安全性.

如果要在线程内结束线程,可以在线程内调用 AfxEndThread.结束线程的两种方式1、这是最简单的方式,也就是让线程函数执行完成,此时线程正常结束.return会返回一个值,一般0是成功结束,当然你可以定义自己的认为合适的值来代表线程成功执行.2、在线程内调用AfxEndThread将会直接结束线程,此时线程的一切资源都会被回收.

  不管是工作者线程还是界面线程,如果你想在线程结束后得到它的结果,那么你可以调用: ::GetExitCodeThread函数

1.2创建UI线程

      创建UI线程与创建工作者线程具有截然不同的过程,工作者线程的行为有其线程函数定义,而UI线程的行为却是由从CWinThread派生来的可动态创建的类控制的,该类与从CWinApp派生的应用程序类很相似。下面给出的UI线程类创建一个顶层框架窗口,用鼠标左键单击时窗口会自动关闭,关闭窗口的同时也结束了线程。原因是窗口关闭时CWnd::OnNcDestroy()会给线程的消息队列发送一个WM_QUIT消息。给主线程发送一个WM_QUIT消息也可以结束主线程及应用程序。

[c-sharp]  view plain  copy
  1. //UIThread.h  
  2. class CUIThread : public CWinThread  
  3. {  
  4.     DECLARE_DYNCREATE(CUIThread)  
  5. public:  
  6.     virtual BOOL InitInstance();  
  7. };  
  8. //UIThread.cpp  
  9. IMPLEMENT_DYNCREATE(CUIThread , CWinThread)  
  10. BOOL CUIThread::InitInstance()  
  11. {  
  12.     m_pMainWnd=new CMainWindow;  
  13.     m_pMainWnd->ShowWindow(SW_SHOW);  
  14.     m_pMainWnd->UpdateWindow();  
  15.     return true;  
  16. }  
  17. //MainWindow.h  
  18. class CMainWindow : public CFrameWnd  
  19. {  
  20. public:  
  21.     CMainWindow();  
  22. protected:  
  23.     afx_msg void OnLButtonDown(UINT , CPoint);  
  24.     DECLARE_MESSAGE_MAP()  
  25. };  
  26. //MainWindow.cpp  
  27. BEGIN_MESSAGE_MAP(CMainWindow , CFrameWnd)  
  28.     ON_WM_LBUTTONDOWN()  
  29. END_MESSAGE_MAP()  
  30. CMainWindow::CMainWindow()  
  31. {  
  32.     Create(NULL , _T("UI Thread Window") );  
  33. }  
  34. void CMainWindow::OnLButtonDown(UINT nFlags , CPoint point)  
  35. {  
  36.     PostMessage(WM_CLOSE , 0 , 0);  
  37. }  

      通过调用AfxBeginThread()来启动CUIThread,该函数接收指向线程类的CRuntiemClass指针:

      CWinThread* pThread=AfxBeginThread(RUNTIME_CLASS(CUIThread));一旦该线程启动,UI线程就会与创建它的线程异步运行了。

1.3暂停和继续执行线程

      运行中的线程可以用CWinThread::SuspendThread()暂停,再用CWinThread::ResumeThread()继续执行。线程可以自己调用ResumeThread(),也可以是别的线程为它调用ResumeThread(),但是暂停的线程不能自己调用ResumeThread()恢复执行,必须是其它线程为它调用ResumeThread()。暂停的线程不会占用处理器时间,并且给系统增加的开销几乎等于0。

      对于每个线程,系统都维护着一个“暂停数”,其值由SuspendThread()加1、由ResumeThread()减1。只有在其“暂停数”为0时,才会给线程调度处理器时间,如果SuspendThread()被连续调用了2次,ResumeThread()也必须被调用2次。没有用CREATE_SUSPENDED标志创建的线程初始“暂停数”为0。用CREATE_SUSPENDED标志创建的线程开始就具有“暂停数”为1。SuspendThread()和ResumeThread()都返回线程以前的“暂停数”,这样你就可以确保线程被继续执行了,无论暂停数有多大都可以反复调用ResumeThread()直到其返回值为1.如果线程当前没有被暂停,ResumeThread()就会返回0。

1.4使线程睡眠

      通过调用API函数::Sleep,线程可以让自己睡眠指定的一段时间,正在睡眠的线程不占用处理器时间。语句::Sleep(10000)使当前线程睡眠10秒钟。

     ::Sleep还用来放弃剩余的线程时间片,Sleep(0)暂停当前线程并允许 调度程序 运行其它具有“相同或更高的优先级别的线程”。如果没有其它“相同或更高的优先级别的线程”处于等待状态,那么函数调用就立即返回继续执行当前的线程。

     传递给::Sleep的值并不能保证线程在指定的时间间隔过去后的某个精确时刻醒过来。线程可能睡眠10秒,也可能睡眠20秒,这要取决于操作系统。在实际中,线程通常会在指定的时间间隔过去之后很短的时间内继续运行。现在,所有的Windows版本中还不存在能够以精确的时间来暂停线程的方法。

1.5终止线程

      线程开始执行后,有两种方法可以终止它,对于工作者线程:1、当函数执行return语句时2、此线程中任何地方的任何函数调用AfxEndThread时,工作者线程就会结束。对于UI线程:1、给自己的消息队列发送WM_QUIT消息(使用API函数::PostQuitMessage())2、线程中自己调用AfxEndThread,UI线程就会结束。在线程结束之后可以用::GetExitCodeThread()检索得到一个32位出口代码,如下语句:

DWORD dwExitCode;

::GetExitCodeThread(pThread->m_hThread , &dwExitCode);

如果对正在执行的线程调用该函数,就会将dwExitCode设置为STILL_ACTIVE(0x103)。在本例中传递给GetExitCodeThread的句柄是从封装线程的CWinThread对象的数据成员m_hThread中得到的。

1.6自动删除CWinThread

      上节中的两行代码看上去非常正确,而实际上它们却是即将发生的事故,除非意识到了CWinThread所具有的某些特殊性质并采取具体行动去处理它。

      已经知道AfxBeginThread创建一个CWinThread对象并将其地址返回给调用者,但是怎样删除CWinThread呢?MFC会在线程结束后通过指针来调用delete删除CWinThread对象,而且CWinThread的析构函数会使用::CloseHandle函数来关闭线程句柄。从表面上看,MFC自动删除CWinThread对象并关闭相应的线程句柄好像很方便。如果MFC不处理这些杂务,你就必须亲自处理它们。但是这里存在一个问题,至少是一个潜在的问题,在看一下语句:

::GetExitCodeThread(pThread->m_hThread , &dwExitCode);

如果线程没有结束,这行代码丝毫没有错误,因为pThread仍然是有效的指针。但是,如果线程已经结束,那么MFC就很可能已经删除了CWinThread对象,现在pThread就是无效指针了。(说“很可能”是因为 线程结束 和 关联的CWinThread对象删除 之间有一段短暂的时间)一个明了的解决办法就是:在线程结束之前将线程句柄CWinThread对象复制到本地变量中,并在对::GetExitCodeThread()的调用中使用此句柄,如下所示:

HANDLE hThread=pThread->m_hThread;

......

::GetExitCodeThread(hThread , &dwExitCode);

但是此段代码也是错误的,因为如果CWinThread对象不再存在的话,线程句柄也不会存在;它早已被关闭了。

      幸运的是,实际上解决此问题有2中办法:第一种通过设置对象的m_bAutoDelete数据成员为FALSE可防止线程结束时MFC删除CWinThread对象。默认值为true允许自动删除。如果选择这种方法,一定记住要通过AfxBeginThread返回的CWinThread指针来调用delete,否则你的应用程序就可能因内存不足而无法运行。代码如下:

[c-sharp]  view plain  copy
  1. CWinThread* pThread=AfxBeginThread(ThreadFunc,NULL,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);  
  2. pThread->m_bAutoDelete=false;  
  3. pThread->ResumeThread();  
  4. //,,,sometime later  
  5. DWORD dwExitCode;  
  6. ::GetExitCodeThread(pThread->m_hThread , &dwExitCode);  
  7. if(dwExitCode==STILL_ACTIVE)  
  8. {  
  9.       //the thread is still running  
  10. }  
  11. else  
  12. {  
  13.       //the thread has terminated.Delete the CWinThread object.  
  14.       delete pThread;  
  15. }  

      创建线程先使其处于暂停状态,如果不这样做,一种发生的可能性很小,但是确实存在的事情就会发生:在创建它的线程设置m_bAutoDelete为false之前,新线程已经结束它的生命期了。要记住,一旦启动了线程,Windows就无法保证给线程分配多少CPU时间了。

      第二种:允许CWinThread对象执行自动删除,但是要使用Win32::DuplicateHandle函数创建一个线程句柄的复件(在暂停状态下)。系统内核维护了线程句柄的引用计数,使用Win32::DuplicateHandle复制一个线程句柄,引用计数将增加1。所以,当CWinThread的析构函数调用::CloseHandle()时,句柄实际上并没有关闭,仅仅是递减了它的引用计数。这种方法的缺陷是必须亲自调用::CloseHandle来关闭句柄,代码如下:

[cpp]  view plain  copy
  1. CWinThread* pThread=AfxBeginThread(ThreadFunc,NULL,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);  
  2. HANDLE hThread;  
  3. ::DuplicateHandle(GetCurrentProcess(),pThread->m_hThread,GetCurrentProcess(),&hThread,0,fasle,DUPLICATE_SAME_ACCESS);  
  4. pThread->ResumeThread();  
  5. //...sometime later  
  6. DWORD dwExitCode;  
  7. ::GetExitCodeThread(pThread->m_hThread , &dwExitCode);  
  8. if(dwExitCode==STILL_ACTIVE)  
  9. {  
  10.       //the thread is still running  
  11. }  
  12. else  
  13. {  
  14.       //the thread has terminated.Delete the CWinThread object.  
  15.       delete pThread;  
  16. }  

 

1.7结束另一个线程

       一般而言,线程只能结束它们自己。如果想让线程A结束线程B,就必须建立一个发信机制,允许线程A告诉线程B结束执行。一个简单的变量可以作为终止请求标志,说明如下:

[c-sharp]  view plain  copy
  1. //Thread A  
  2. nContinue=1;  
  3. CWinThread* pThread=AfxBeginThread(ThreadFunc,&nContinue);  
  4. ...  
  5. nContinue=0;//tell Thread B to terminate  
  6. -----------------------------------------------  
  7. //Thread B  
  8. UINT ThreadFuncB(LPVOID pParam)  
  9. {  
  10.       int* pContinue=(int*)pParam;  
  11.       while(*pContinue)  
  12.       {  
  13.             //work.work.work  
  14.       }  
  15.       return 0;  
  16. }  

      在本例中线程B要经常检查nContinue,如果nContinue从非0值变为0则线程结束,通常让2个线程在没有同步化的情况下访问同一个变量并不是很好的主意,但是在本例中可以这样做,因为线程B只是进行检查来确定nContinue是否为0,通常,把nContinue设置为全局变量。

      现在假设你想要修改本例,使线程A将nContinue设置为0,然后它就暂停直到线程B不在允许为止。下面的代码给出了一种实现此目的的方法:

[c-sharp]  view plain  copy
  1. //Thread A  
  2. nContinue=1;  
  3. CWinThread* pThread=AfxBeginThread(ThreadFunc,&nContinue);  
  4. ...  
  5. nContinue=0;//tell Thread B to terminate  
  6. ::WaitForSingleObject(hThreadB,INFINITE);  
  7. -----------------------------------------------  
  8. //Thread B  
  9. UINT ThreadFuncB(LPVOID pParam)  
  10. {  
  11.       int* pContinue=(int*)pParam;  
  12.       while(*pContinue)  
  13.       {  
  14.             //work.work.work  
  15.       }  
  16.       return 0;  
  17. }  

      ::WaitForSingleObject(hThreadB,INFINITE);阻止 调用线程 直到指定对象(线程B的句柄)进入有信号状态(一个线程在结束后就会处于有信号状态,而在执行过程中,处于无信号状态-nonsignaled);在::WaitForSingleObject中线程A暂停执行,它会处于有效的等待状态,直到该函数返回。在本例中假定了线程B在线程A通知它之前不会结束。如果情况不是这样(),线程A就必须在暂停状态下创建线程B并用::DuplicateHandle复制一个线程句柄。否则,线程A就会陷入给::WaitForSingleObject传递一个无效句柄的麻烦。

      ::WaitForSingleObject是一个必不可少的函数,你在编写多线程程序时会多次用到它。传递给它的第一个参数是自己想等待的句柄,第二个参数是希望等待的时间长度,指定了INFINITE后,如果等待的对象永远不会进入有信号状态,那么调用线程就会一直等待。如果指定了一个毫秒值,如下:::WaitForSingleObject(hThread,5000);该函数就会在指定的时间用完之后返回,即使对象还没有进入有信号状态。可以通过检查返回值来确定函数返回的原因,例如:::WaitForSingleObject(hThread,0);把等待时间指定为0该函数就不会等待而直接返回,返回值为WAIT_OBJECT_0_意味着线程进入了有信号状态(线程已经运行完毕,不再存在);返回值为WAIT_TIMEOUT说明线程没有进入信号发出状态(线程还在运行);

 2线程同步:

      在现实世界中,开始一个线程,必须将它的动作与应用程序中的其它线程相协调。例如:如果2个线程共享一个链表,那么对该链表的访问必须被串行化,这样2个线程才不会在同一时间修改链表。Windows支持4种类型的同步对象,可以用来同步“由并发运行的线程所执行的”操作:

      @临界区(关键代码段)

      @互斥对象

      @事件对象

      @信号量

MFC在名为CCriticalSection、CMutex、CEvent和CSemaphore的类中封装了这些对象。下面的小节将描述如何使用这些类来同步 并发执行 的线程操作。

2.1临界区

      最简单的线程同步对象就是临界区,临界区用来 串行化 由 2个或多个 线程 共享的链表、简单变量、结构和其它资源的访问。这些线程必须属于相同的进程,因为临界区不能跨进程的边界工作。

      临界区背后的思想就是:每个“独占性的访问一个资源”的线程在访问那个资源之前锁定临界区,访问完成之后解除锁定。如果线程B试图锁定当前由线程A锁定的临界区,线程B将阻塞直到该临界区空闲。阻塞时,线程B处在一个十分有效的等待状态,它不消耗处理器时间。

      CCriticalSection::Lock锁定临界区,而CCriticalSection::Unlock解除对临界区的锁定。一般使用全局的同步对象,以确保同步对象对于进程中的所有线程都是可见的,但是同步对象不一定必须具有全局范围。

[c-sharp]  view plain  copy
  1. //Global data  
  2. CCriticalSection g_cs;  
  3. ......  
  4. //Thread A  
  5. g_cs.Lock();  
  6. //write to the linked list  
  7. g_cs.Unlock();  
  8. ......  
  9. //Thread B  
  10. g_cs.Lock();  
  11. //Read from the linked list.  
  12. g_cs.Unlock();  

      现在线程A和线程B要同时访问该链表是不可能的,因为他们是用相同的 临界区 来保护链表。对于一个简单的变量,也应该使用一个临界区来保护,因为在C++ 中一个看起来十分简单的原子操作(如:nVal++)也要编译成具有许多 机器指令 的序列。而在任何2个机器指令之间,一个线程可以剥夺另一个线程的机器指令。为防止 任何数据 遭受 同时发生的写入访问 或者 同时发生的读取和写入访问 ,临界区是执行这项工作完美的工具。

      win32 API包含一组名为InterLockedInCrement、InterLockedDecrement、InterLockedExchange、InterLockedCompareExchange和InterLockedExchangeAdd的函数,你可以用它们来安全的操作32位的值,而不需显式的使用同步对象。例如:nVal是UINT、DWORD或者其它的32位数据类型,那么你就可以使用下面的语句使之递增:::InterLockedIncrement(&nVal);系统将确保其它使用InterLocked函数执行对nVal的访问不会重叠。

2.2互斥量

      Mutex是单词mutually和exclusive的缩写。与临界区一样,互斥量也是用来获得“由2个或者更多线程共享的”资源 的独占性访问权。与临界区不同的是,互斥量可以用来同步在相同进程或不同进程上运行的线程。对于进程内线程同步的需要,临界区一般要优于互斥量,因为临界区更快。但如果希望同步在两个或者多个不同进程上运行的线程,那么互斥量就更合适了。

      假定两个应用程序使用一块 共享内存 来交换数据。在该 共享内存 内部是一个必须防止并发线程访问的链表。临界区就不能用了,因为它不能跨越进程的边界,但是互斥量可以可以出色的完成任务。下面就是在读取或者写入链表之前你在每个进程上有做的工作:

[c-sharp]  view plain  copy
  1. //Global data  
  2. CMutex g_mutex(false,"MyMutex");  
  3. ......  
  4. g_mutex.Lock();  
  5. //Read or Write the linked List  
  6. g_mutex.Unlock();  

      传递个CMutex构造函数的第一个参数指定互斥量的初始状态是锁定(true)或者没有锁定(false)。第二个参数指定互斥量的名称,如果该互斥量是用来同步在两个不同进程上的线程,就需要这个名称。两个进程必须使用相同的名称才能同步2个不同进程上的线程,这样两个CMutex对象将引用Windows内核中相同的互斥量对象。

      默认情况下,Lock将永远等待直到互斥量变为没有锁定。可以通过指定一个以毫秒为单位的 最大等待时间 来建立一个安全失败机制,Lock的返回值告诉你函数调用返回的原因。一个非0的返回值表示互斥量空闲,而0表示超时时间段到期了,此时不访问共享资源是比较谨慎的,因为访问共享资源可能会导致重叠访问。因此使用Lock的超时特性的代码一般如下:if(g_mutex.Lock(1000)){//Read or Write the Linked List    g_mutex.Unlock(); }

      互斥量和临界区之间还有另外一个差别,如果一个线程锁定了临界区而始终没有解除临界区的锁定(即使该线程终止了),那么等待临界区空闲的其它线程将无限期的阻塞下去。然而,锁定互斥量的线程不能在其终止前解除互斥量的锁定,那么系统将认为互斥量被放弃了并自动释放该互斥量,这样等待进程就可以继续执行。

3.1消息泵

      编程人员常误以为多线程可以使程序运行的更快。在单处理机上,多线程不会加快应用程序的运行速度;然而,它确实使应用程序的“响应更”快了。有一个例子可以说明多线程对响应速度的作用。编写一个应用程序,让它响应某菜单命令,画几千个椭圆。如果由主线程画椭圆,则主线程便不能抽空 检查消息队列 和 分派等待消息,因此输入就会被冻结,直到画椭圆循环执行完毕。如果画椭圆部分由独立线程完成,则在画椭圆时应用程序还能响应用户的输入。

      然而在这样简单的应用程序中使用多线程未免小题大作。另一个解决方案是:主线程画椭圆时,可以使用“消息泵”保证消息的流通。假定画椭圆的消息处理程序是这样的:

 

[cpp]  view plain  copy
  1. void CMainWindow::OnStartDrawing()  
  2. {  
  3.       for(int i=0;i<NUMELLIPOES;;i++)  
  4.       {  
  5.             DrawRandomEllipse();  
  6.       }  
  7. }  
 

 

如果NUMELLIPSES是很大的数字,则for循环一旦启动,程序就会停滞较长的时间。针对这种情况,可以试着添加另一个菜单命令,设置一个标记并截断for循环语句,如下所示:

[c-sharp]  view plain  copy
  1. void CMainWindow::OnStartDrawing()  
  2. {  
  3.       for(int i=0;i<NUMELLIPSES && !m_bQuit ; i++)  
  4.       {  
  5.             DrawRandomEllipse();  
  6.       }  
  7. }  
  8. void CMainWindow::OnStopDrawing()  
  9. {  
  10.       m_bQuit=true;  
  11. }  
 

但这还是不行,因为只要OnStartDrawing()中的for循环一旦开始,MFC内部的SendMessage()函数就不会返回,直到该函数执行结束,所以OnStopDrawing()函数根本得不到执行的机会,也就不能响应菜单命令了。

      如果使用“消息泵”这个问题就能很好的解决,在单线程MFC程序中,最好使用下面的方法执行冗长的过程:

[cpp]  view plain  copy
  1. void CMainWindow::OnStartDrawing()  
  2. {  
  3.       for(int i=0;i<NUMELLIPSES && !m_bQuit ; i++)  
  4.       {  
  5.             DrawRandomEllipse();  
  6.             if( !PeekAndPump() )  
  7.                   break;  
  8.       }  
  9. }  
  10. void CMainWindow::OnStopDrawing()  
  11. {  
  12.       m_bQuit=true;  
  13. }  
  14. void CMainWindow::OeekAndPump()  
  15. {  
  16.       MSG msg;  
  17.       while(::PeekMessage(&msg,NULL,0,0,PM_MOREMOVE))  
  18.       {  
  19.             if(!AfxGetApp()->PumpMessage())  
  20.             {  
  21.                   ::PostQuitMessage(0);  
  22.                   return false;  
  23.             }  
  24.       }  
  25.       LONG lIdle;  
  26.       while(AfxGetApp()->OnIdle(lIdle++))  
  27.             return true;  
  28. }  
 

在每次for循环的最后,要通过函数PeekMessage()函数看看消息队列中是否有消息在等待,如果消息队列中有消息,则调用CWinThread::PumpMessage()提取和分派消息。有一种特殊情况,如果PumpMessage()返回0,则表示提取和分派的最后一个消息是WM_QUIT_消息,因为只有MFC内部的主消息循环提取WM_QUIT消息,应用程序才能结束,所以该消息要求特殊处理,通过::PostMessage()函数把另外一个WM_QUIT_消息发往应用程序队列。使主应用程序响应 结束程序 的消息。

      如果WM_QUIT消息不提示提前退出,那么通过在PeekAndPump返回之前调用应用程序对象的OnIdle,PeekAndPump就可模仿主结构的闲置机制。

3.2执行其它进程

      Win32进程执行其它进程和执行线程一样容易,下列语句执行c:/windows/Notepad.exe

 

 

[cpp]  view plain  copy
  1. STARTUPINFO si;  
  2. ::ZeroMemory(&si,sizeof(STARTUPINFO));  
  3. si.cb=sizeof(STARTUPINFO);  
  4. PROGRESS_INFORMATION pi;  
  5. if()  
  6. {  
  7.       ::CloseHandle(pi.hThread);  
  8.       ::CloseHandle(pi.hProcess);  
  9. }  

::CreateProcess()是一个通用函数,它获得可执行文件的名字(和路径),然后加载并执行它。如果可执行文件中的驱动器和目录名被省略,则系统自动在windows目录、windows系统目录、当前路径下的所有目录和path变量中的其它目录中搜索该文件。文件名也可以包含命令行参数,如:"c://windows//Notepad c://windows//Desktop//Ideas.txt"。

      ::CreateProcess将进程的关键信息填充在PROGRESS_INFORMATION结构中,相关信息包括:进程句柄(bProcess)和进程中主线程的句柄(hThread).。在进程启动后,要用CloseHandle关闭这些句柄如果不再用这些句柄,只要CreateProcess一返回,就可以关闭它们。如上面的代码就是一返回就关闭相关的进程和线程句柄。

      如果CreateProcess返回非零值,则意味着进程启动成功。因为Win32是异步启动,异步执行的,所以CreateProcess不必等到进程结束后再返回。如果你希望启动另一个进程,并暂停当前进程 直到 该进程启动的进程结束,则你可以对该进程句柄调用WaitForSingleObject如下所示:

[cpp]  view plain  copy
  1. STARTUPINFO si;  
  2. ::ZeroMemory(&si,sizeof(STARTUPINFO));  
  3. si.cb=sizeof(STARTUPINFO);  
  4. PROGRESS_INFORMATION pi;  
  5. if(::CreateProcess(NULL,"c://windows//NotePad",NULL,NULL,false,NORMAL_PRIORITY_CLASS,NULL,NULL,&si,π))  
  6. {  
  7.       ::CloseHandle(pi.hThread);  
  8.       ::WaitForSingleObject(pi.hProcess,INFINITE);  
  9.       ::CloseHandle(pi.hProcess);  
  10. }