精华内容
下载资源
问答
  • 如标题,在MFC中必须使用 AfxBeginThread创建多线程,如使用BeginThread可能会出现BUG
  • 多线程mfc进度条

    热门讨论 2013-04-12 17:16:30
    多线程进度条控制原型示例。处理耗时的任务时,弹出进度条而界面不会失去响应。
  • MFC多线程技术

    千次阅读 2019-03-11 21:03:44
    MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。 工作者线程没笑消息机制,通常用来执行后台计算和维护任务,如...

    MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。

    工作者线程没笑消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程之外的用户输入,响应用户及系统产生的事件和消息等。但对于Win32的API编程而言,这两种编程是没有区别的,他们都只需要线程的启动地址即可启动线程来执行任务。

    在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。这两种函数的重载和原型分别说明如下:

    (1)工作者线程

    CWndThread *AfxBeginThread(AFX_THREADPROC pfnThreadProc,
        LPVOID pParam,
        UINT nPriority=THREAD_PRIORITY_NORMAL,
        UINT nStackSize = 0,
        DWORD dwCreateFlags = 0,
        LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

    (2)IU线程(用户界面线程)

    CWndThread *AfxBeginThread(CRuntimeClass *pThreadClass,
        int nPriority=THREAD_PRIORITY_NORMAL,
        UINT nStackSize = 0,
        DWORD dwCreateFlags = 0,
    LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL);

    AfxBeginThread()创建线程的流程不论哪个AfxBeginThread(),首先都是创建MFC线程对象,然后创建Win32线程对象。

     

                               AfxBeginThread创建线程的流程图

    MFC线程技术剖析

    MFC的核心类库中有一个名为CWinThread的类,这个类在MFC的底层机理中占举足轻重的地位。

                            MFC应用程序 

        

     

    线程状态用类_AFX_THREAD_STATE描述,模块状态用类_AFX_MODULE_STATE描述,模块-线程状态用类_AFX_MODULE_THREAD_STATE描述。这些类从类CNoTrackObject派生。进程状态用类_AFX_BASE_MODULE_STATE描述,从模块状态_AFX_MODULE_STATE派生。进程状态是一个可以独立执行的MFC应用程序的模块状态。还有其他状态如DLL的模块状态等也从模块状态类_AFX_MODULE_STATE派生。

                                     MFC状态类的层次

     

                                  模块、线程、模块-线程状态的关系

     

    多线程实践案例:(多线程文件查找器)

    查找文件的时候,首先用FindFirstFile函数,如果函数执行成功,返回句柄hFindFile来对应这个寻找操作,接下来可以利用这个句柄循环调用FindNextFile函数继续查找其他文件,知道该函数返回失败(FALSE)为止。最后还要调用FindClose函数关闭hFindFile句柄。

    hFindFile = ::FindFirstFile(lpFileName,lpFindData);
    
    if(hFindFile != INVALID_HANDLE_VALUE)
    {
        do // 处理本次找到的文件
        {
    
    }while(::FindNextFile(lpFileName,lpFindData));
    ::FindColse(hFindFile);
    }

    文件搜索器要在指定的目录及所有子层目录中查找文件,然后向用户显示出查找的结果。如果使用多线程的话,就意味着各线程要同时在不同目录中搜索文件。

    这个程序最关键的地方是定义了一个动态的目录列表。

    CTypedSimpleList<CDirectoryNode *> m_listDir;
    struct CDirectoryNode : public CNoTrackObject
    {
        CDirectoryNode* pNext; // CTypedSimpleList类模板要用次成员
        char szDir[MAX_PATH];  // 要查找的目录
    }

    在线程执行查找文件任务的时候,如果找到的是目录就将它添加到列表中,若找到的是文件,就用自定义CheckFile函数进行比较,判断是否符合查找条件,若符合就打印出来,显示给用户。线程在查找完一个目录以后,再从m_listDir列表中取出一个新的目录进行查找,同时将该目录对应的结点从表中删除。

    当m_listDir为空时,线程就要进入暂停状态,等待其他线程向m_listDir中添加新的目录。

    案例:

    RapidFile.h文件

    #pragma once
    #include <afxwin.h>
    
    struct CDirectoryNode : public CNoTrackObject  // 创建文件夹目录结构体
    {
        CDirectoryNode *pNext;  // 文件夹目录的下一个指针
        char szDir[MAX_PATH];   // 文件夹名称
    }
    
    class CRapidFinder
    {
    public:
        CRapidFinder(int nMaxThread);      // 构造函数
        virtual ~CRapidFinder();        // 虚析构函数
        
        BOOL CheckFile(LPCTSTR lpszFileName);  // 匹配文件夹名字
        
        int m_nResultCount; // 结果的数量
        int m_nThreadCount;  // 活动线程的数量
        
        CTypeSimpleList<CDirectoryNode *> m_listDir;  // 文件夹列表
        CRITICAL_SECTION m_cs; // 临界区
        
        const int m_nMaxThread;  // 最大线程数量
        char m_szMatchName[MAX_PATH]; // 最大搜索的文件
        
        // 通知线程的工作状态
        HANDLE m_hDirEvent; //我们向m_listDir添加新的目录,10个线程 9个停止,1个工作 若m_listDir为空,线程不能停止
        HANDLE m_hExitEvent; // 各个搜索线程是否已经结束
    }

    RapidFile.cpp文件

    #include "RapidFile"
    #include <string>
    
    CRapidFinder::CRapidFinder(int nMaxThread) : m_nMaxThread(nMaxThread)
    {
        m_nResultCount = 0;
        m_nThreadCount = 0;
        m_szMatchName[0] = '\0';
        
        m_listDir.Construct(offsetof(CDirectoryNode,pNext)); // 创建CTypedSimpleList
        m_hDirEvent = ::CreateEvent(NULL,FALSE,FALSE,NULL);
        m_hExitEvent = ::CreateEvent(NULL,FALSE,FALSE,NULL);
        ::InitializeCriticalSectioin(&m_cs);
    }
    
    CRapidFinder::~CRapidFinder()
    {
        ::CloseHandle(m_hDirEvent);
        ::CloseHandle(m_hExitEvent);
        ::DeleteCriticalSection(&m_cs);
    }
    
    // 查找文件名
    BOOL CRapidFinder::CheckFile(LPCTSTR lpszFileName)
    {
        char str[MAX_PATH];
        char strSearch[MAX_PATH];
        strcpy(str,lpszFileName);
        strcpy(strSearch,m_szMatchName);
        
        _strupr(str); // 将字符串全部转换为大写
        _strupr(strSearch);
        
        if(strstr(str,strSearch) != NULL)  // 查找的文件名在里面
        {
            return TRUE;
        }
        return FALSE;
    }

    MultiThreadFindFile.cpp

    #include <stdio.h>
    #include <afxwin.h>
    #include "RapidFile.h"
    
    UINT FinderEntry(LPVOID lpParam)
    {
        CRapidFinder *pFinder = (CRapidFinder *)lpParam;
        CDirectoryNode *pNode = NULL;  // m_listDir从pNode中获取
        BOOL bActive = TRUE; // 线程状态
        
        // 只要m_listDir有目录
        while(1)
        {
            // 取出新目录 互斥的取待查目录
            ::EnterCriticalSection(&pFinder->m_cs);
            if(pFinder->m_listDir.IsEmpty())
                bActive = FALSE;
            else
            {
                pNode = pFinder->m_listDir.GetHead();
                pFinder->m_listDir.Remove(pNode);
            }
            ::LeaveCriticalSection(&pFinder->m_cs);
            
            // bActive指示了当前线程的工作状态,如果m_listDir队列当前为空,那么我们当前线程先等待
            if(!bActive)
            {
                ::EnterCriticalSection(&pFinder->m_cs);
                pFinder->m_nThreadCount--; 
                if(pFinder->m_nThreadCount == 0)
                {
                    ::LeaveCriticalSection(&pFinder->m_cs);
                    break;
                }
                ::LeaveCriticalSection(&pFinder->m_cs);
                
                // 进入等待状态
                ResetEvent(pFinder>m_hDirEvent);
                ::WaitForSingleObject(pFinder->m_hDirEvent,INFINITE);
                
                ::EnterCriticalSection(&pFinder->m_cs);
                // 此时当前线程再度获得CPU的推进机会
                pFinder->m_nThreadCount++; // 当前的活动线程数量加1
                ::LeaveCriticalSection(&pFinder->m_cs);
                
                bActive = TRUE;
                continue;
            }
            
            // 实现基于pNode的目录查找
            WIN32_FIND_DATA fileData;
            HANDLE hFindFile;
            if(pNode->szDir[strlen(pNode->szDir)-1] != '\')
                strcat(pNode->szDir,'\\');
            strcat(pNode->szDir,"*.*");
            hFindFile = ::FindFirstFile(pNode->szDir,&fileData);
            
            if(hFindFile != INVALID_HANDLE_VALUE)
            {
                do
                {
                    if(fileData.cFileName[0] == '.')
                        continue;
                    if(fileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
                    { // 是目录,添加到m_listDir
                        CDirectoryNode *p = new CDirectoryNode;
                        strncpy(p->szDir,pNode->szDir,strlen(pNode->szDir)-3);
                        strcat(p->szDir,fileData.cFileName);
                        
                        ::EnterCriticalSection(&pFinder->m_cs);
                        pFinder->m_listDir.AddHead(p);
                        ::LeaveCriticalSection(&pFinder->m_cs);
                        // 置信一个事件
                        ::SetEvent(pFinder->m_hDirEvent);
                    }
                    else // 文件
                    {
                        if(pFinder->CheckFile(fileData.cFileName)) // 找到文件名
                        {
                            ::EnterCriticalSection(&pFinder->m_cs);
                            ::InterlockedIncrement((long *)&pFinder->m_nResultCount);
                            ::LeaveCriticalSection(&pFinder->m_cs);
                            printf("%s \n",fileData.cFileName);
                        }
                    }
                }while(::FindNextFile(pNode->szDir,&fileData));
            }
            // 此节点的保存的目录已经全部搜索完毕
            delete pNode;
            pNode = NULL;
        }
        ::SetEvent(pFinder->m_hExitEvent);
        // 判断当前线程是否是最后一个结束循环的线程
        if(::WaitForSingleObject(pFinder->m_hDirEvent,0) != WAIT_TIMEOUT)
        { // 通知主线程,最后一个搜索线程已经结束了
            ::SetEvent(pFinder->m_hExitEvent);
        }
        
        return 0;
    }
    
    int main(void)
    {
        CRapidFinder *pFinder = new CRapidFinder(64); // 开64个线程
        CDirectoryNode *pNode = new CDirectoryNode; // 创建结点
        
        char szPath[] = "C:\\";  // 需要查找的目录
        char szFile[] = "stdafx";    // 需要查找的字符串
        
        // 对CRapider的信息进行设置
        strcpy(pNode->szDir,szPath);    // 设置要搜索的目录
        pFinder->m_listDir.AddHead(pNode); // 将要搜索的目录添加到list中,当做头结点
        strcpy(pFinder->m_szMatchName,szFile); // 需要搜索的文件名
        
        // 创建辅助线程
        pFinder->m_nThreadCount = pFinder->m_nMaxThread;
        
        // 创建辅助线程,并等待查找结束
        for(int i =0; i < pFinder->m_nMaxThread; i++)
        {
            AfxBeginThread(FinderEntry,pFinder);
        }
        WaitForSingleObject(pFinder->m_hExitEvent,INFINITE);
        // 打印查找结果
        printf("一共找到同名文件%d个\n");
        
        delete pFinder;
        
        system("pause");
        return 0;
    }
    
    展开全文
  • MFC界面开发项目中,很时候会因为程序操作数据需要显示进度条来显示需要等待的需求,这里的代码使用开线程的方式挂起一个进度条窗口,很好的满足这个需求。
  • MFC多线程各种线程用法

    万次阅读 多人点赞 2018-06-25 15:15:41
    一、问题的提出 编写一个耗时的单线程程序: 新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG添加一个按钮,ID为 IDC_SLEEP_SIX_SECOND,标题为“延时6秒”,添加按钮的响应函数,...
    1. 一、问题的提出  
    2.   
    3. 编写一个耗时的单线程程序:  
    4.   
    5.   新建一个基于对话框的应用程序SingleThread,在主对话框IDD_SINGLETHREAD_DIALOG添加一个按钮,ID为 IDC_SLEEP_SIX_SECOND,标题为“延时6秒”,添加按钮的响应函数,代码如下:   
    6.   
    7. void CSingleThreadDlg::OnSleepSixSecond()   
    8.   
    9. {  
    10.   
    11. Sleep(6000); //延时 6秒  
    12.   
    13. }  
    14.   
    15.   编译并运行应用程序,单击“延时6秒”按钮,你就会发现在这6秒期间程序就象“死机”一样,不在响应其它消息。为了更好地处理这种耗时的操作,我们有必要学习——多线程编程。  
    16.   
    17. 二、多线程概述  
    18.   
    19.   进程和线程都是操作系统的概念。进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。  
    20.   
    21.   线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主执行线程,主执行线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给Windows系统。主执行线程终止了,进程也就随之终止。  
    22.   
    23.   每一个进程至少有一个主执行线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。  
    24.   
    25.   多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。要说明的一点是,目前大多数的计算机都是单处理器(CPU)的,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。由此可见,如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。这一点在多线程编程时应该注意。  
    26.   
    27.   Win32 SDK函数支持进行多线程的程序设计,并提供了操作系统原理中的各种同步、互斥和临界区等操作。Visual C++ 6.0中,使用MFC类库也实现了多线程的程序设计,使得多线程编程更加方便。  
    28.   
    29. 三、Win32 API对多线程编程的支持  
    30.   
    31.   Win32 提供了一系列的API函数来完成线程的创建、挂起、恢复、终结以及通信等工作。下面将选取其中的一些重要函数进行说明。   
    32.   
    33. 1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,  
    34.   
    35. DWORD dwStackSize,  
    36.   
    37. LPTHREAD_START_ROUTINE lpStartAddress,  
    38.   
    39. LPVOID lpParameter,  
    40.   
    41. DWORD dwCreationFlags,  
    42.   
    43. LPDWORD lpThreadId);  
    44.   
    45. 该函数在其调用进程的进程空间里创建一个新的线程,并返回已建线程的句柄,其中各参数说明如下:  
    46.   
    47. lpThreadAttributes:指向一个 SECURITY_ATTRIBUTES 结构的指针,该结构决定了线程的安全属性,一般置为 NULL;   
    48.   
    49. dwStackSize:指定了线程的堆栈深度,一般都设置为0;   
    50.   
    51. lpStartAddress:表示新线程开始执行时代码所在函数的地址,即线程的起始地址。一般情况为 (LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是线程函数名;   
    52.   
    53. lpParameter:指定了线程执行时传送给线程的32位参数,即线程函数的参数;   
    54.   
    55. dwCreationFlags:控制线程创建的附加标志,可以取两种值。如果该参数为0,线程在被创建后就会立即开始执行;如果该参数为CREATE_SUSPENDED,则系统产生线程后,该线程处于挂起状态,并不马上执行,直至函数 ResumeThread被调用;   
    56.   
    57. lpThreadId:该参数返回所创建线程的ID;   
    58.   
    59. 如果创建成功则返回线程的句柄,否则返回 NULL。   
    60.   
    1. 2、DWORD SuspendThread(HANDLE hThread);  
    1.   
    2. 该函数用于挂起指定的线程,如果函数执行成功,则线程的执行被终止。 

    3. 3、 DWORD ResumeThread(HANDLE hThread);  
    4.   
    5. 该函数用于结束线程的挂起状态,执行线程。 

    6. 4、VOID ExitThread(DWORD dwExitCode);  
    7.   
    8. 该函数用于线程终结自身的执行,主要在线程的执行函数中被调用。其中参数dwExitCode用来设置线程的退出码。
    9.  5、 BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);  
    10.   
    11.   一般情况下,线程运行结束之后,线程函数正常返回,但是应用程序可以调用TerminateThread强行终止某一线程的执行。各参数含义如下:  
    12.   
    13. hThread:将被终结的线程的句柄;   
    14.   
    15. dwExitCode:用于指定线程的退出码。   
    16.   
    17.   使用 TerminateThread()终止某个线程的执行是不安全的,可能会引起系统不稳定;虽然该函数立即终止线程的执行,但并不释放线程所占用的资源。因此,一般不建议使用该函数。   
    18.   
    19. 6、BOOL PostThreadMessage(DWORD idThread,  
    20.   
    21. UINT Msg,  
    22.   
    23. WPARAM wParam,  
    24.   
    25. LPARAM lParam);  
    26.   
    27. 该函数将一条消息放入到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。  
    28.   
    29. idThread:将接收消息的线程的ID;   
    30.   
    31. Msg:指定用来发送的消息;   
    32.   
    33. wParam:同消息有关的字参数;   
    34.   
    35. lParam:同消息有关的长参数;   
    36.   
    37. 调用该函数时,如果即将接收消息的线程没有创建消息循环,则该函数执行失败。  
    38.   
    39. 四、Win32 API多线程编程例程  
    40.   
    41. 例程1 MultiThread1  
    42.   
    43. 建立一个基于对话框的工程MultiThread1,在对话框IDD_MULTITHREAD1_DIALOG中加入两个按钮和一个编辑框,两个按钮的ID分别是IDC_START,IDC_STOP ,标题分别为“启动”,“停止”,IDC_STOP的属性选中Disabled;编辑框的ID为 IDC_TIME ,属性选中Read-only;  
    44.   
    45.     
    46.   
    47. 在MultiThread1Dlg.h文件中添加线程函数声明: void ThreadFunc();  
    48.   
    49. 注意,线程函数的声明应在类CMultiThread1Dlg的外部。 在类CMultiThread1Dlg内部添加protected型变量: HANDLE hThread;  
    50.   
    51. DWORD ThreadID;  
    52.   
    53. 分别代表线程的句柄和ID。   
    54.   
    55.     
    56.   
    57. 在MultiThread1Dlg.cpp文件中添加全局变量 m_bRun : volatile BOOL m_bRun;  
    58.   
    59. m_bRun 代表线程是否正在运行。  
    60.   
    61. 你要留意到全局变量 m_bRun 是使用 volatile 修饰符的,volatile 修饰符的作用是告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。对于多线程引用的全局变量来说,volatile 是一个非常重要的修饰符。  
    62.   
    63. 编写线程函数: void ThreadFunc()  
    64.   
    65. {  
    66.   
    67. CTime time;  
    68.   
    69. CString strTime;  
    70.   
    71. m_bRun=TRUE;  
    72.   
    73. while(m_bRun)  
    74.   
    75. {  
    76.   
    77. time=CTime::GetCurrentTime();  
    78.   
    79. strTime=time.Format("%H:%M:%S");  
    80.   
    81. ::SetDlgItemText(AfxGetMainWnd()->m_hWnd,IDC_TIME,strTime);  
    82.   
    83. Sleep(1000);  
    84.   
    85. }  
    86.   
    87. }  
    88.   
    89. 该线程函数没有参数,也不返回函数值。只要m_bRun为TRUE,线程一直运行。  
    90.   
    91. 双击IDC_START按钮,完成该按钮的消息函数: void CMultiThread1Dlg::OnStart()   
    92.   
    93. {  
    94.   
    95. // TODO: Add your control notification handler code here  
    96.   
    97. hThread=CreateThread(NULL,  
    98.   
    99. 0,  
    100.   
    101. (LPTHREAD_START_ROUTINE)ThreadFunc,  
    102.   
    103. NULL,  
    104.   
    105. 0,  
    106.   
    107. &ThreadID);  
    108.   
    109. GetDlgItem(IDC_START)->EnableWindow(FALSE);  
    110.   
    111. GetDlgItem(IDC_STOP)->EnableWindow(TRUE);  
    112.   
    113. }  
    114.   
    115. 双击IDC_STOP按钮,完成该按钮的消息函数: void CMultiThread1Dlg::OnStop()   
    116.   
    117. {  
    118.   
    119. // TODO: Add your control notification handler code here  
    120.   
    121. m_bRun=FALSE;  
    122.   
    123. GetDlgItem(IDC_START)->EnableWindow(TRUE);  
    124.   
    125. GetDlgItem(IDC_STOP)->EnableWindow(FALSE);  
    126.   
    127. }  
    128.   
    129. 编译并运行该例程,体会使用Win32 API编写的多线程。   
    130.   
    131. 例程2 MultiThread2  
    132.   
    133.   该线程演示了如何传送一个一个整型的参数到一个线程中,以及如何等待一个线程完成处理。  
    134.   
    135. 建立一个基于对话框的工程MultiThread2,在对话框IDD_MULTITHREAD2_DIALOG中加入一个编辑框和一个按钮,ID分别是IDC_COUNT,IDC_START ,按钮控件的标题为“开始”;   
    136.   
    137. 在MultiThread2Dlg.h文件中添加线程函数声明: void ThreadFunc(int integer);  
    138.   
    139. 注意,线程函数的声明应在类CMultiThread2Dlg的外部。  
    140.   
    141. 在类CMultiThread2Dlg内部添加protected型变量: HANDLE hThread;  
    142.   
    143. DWORD ThreadID;  
    144.   
    145. 分别代表线程的句柄和ID。  
    146.   
    147.     
    148.   
    149. 打开ClassWizard,为编辑框IDC_COUNT添加int型变量m_nCount。在 MultiThread2Dlg.cpp文件中添加:void ThreadFunc(int integer)  
    150.   
    151. {  
    152.   
    153. int i;  
    154.   
    155. for(i=0;i<integer;i++)  
    156.   
    157. {  
    158.   
    159. Beep(200,50);  
    160.   
    161. Sleep(1000);  
    162.   
    163. }  
    164.   
    165. }   
    166.   
    167. 双击IDC_START按钮,完成该按钮的消息函数: void CMultiThread2Dlg::OnStart()   
    168.   
    169. {  
    170.   
    171. UpdateData(TRUE);  
    172.   
    173. int integer=m_nCount;  
    174.   
    175. hThread=CreateThread(NULL,  
    176.   
    177. 0,  
    178.   
    179. (LPTHREAD_START_ROUTINE)ThreadFunc,  
    180.   
    181. (VOID*)integer,  
    182.   
    183. 0,  
    184.   
    185. &ThreadID);  
    186.   
    187. GetDlgItem(IDC_START)->EnableWindow(FALSE);  
    188.   
    189. WaitForSingleObject(hThread,INFINITE);  
    190.   
    191. GetDlgItem(IDC_START)->EnableWindow(TRUE);  
    192.   
    193. }  
    194.   
    195. 顺便说一下WaitForSingleObject函数,其函数原型为:DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);  
    196.   
    197. hHandle为要监视的对象(一般为同步对象,也可以是线程)的句柄;   
    198.   
    199. dwMilliseconds为hHandle对象所设置的超时值,单位为毫秒;   
    200.   
    201.   当在某一线程中调用该函数时,线程暂时挂起,系统监视hHandle所指向的对象的状态。如果在挂起的 dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但 hHandle所指向的对象还没有变成有信号状态,函数照样返回。参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为 0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。  
    202.   
    203.   本例程调用该函数的作用是按下IDC_START按钮后,一直等到线程返回,再恢复IDC_START按钮正常状态。编译运行该例程并细心体会。  
    204.   
    205. 例程3 MultiThread3   
    206.   
    207. 传送一个结构体给一个线程函数也是可能的,可以通过传送一个指向结构体的指针参数来完成。先定义一个结构体:   
    208.   
    209. typedef struct  
    210.   
    211. {  
    212.   
    213. int firstArgu,  
    214.   
    215. long secondArgu,  
    216.   
    217. …  
    218.   
    219. }myType,*pMyType;  
    220.   
    221. 创建线程时CreateThread(NULL,0,threadFunc,pMyType,…);  
    222.   
    223. 在threadFunc函数内部,可以使用“强制转换”:  
    224.   
    225. int intValue=((pMyType)lpvoid)->firstArgu;  
    226.   
    227. long longValue=((pMyType)lpvoid)->seconddArgu;  
    228.   
    229. ……  
    230.   
    231. 例程3 MultiThread3将演示如何传送一个指向结构体的指针参数。   
    232.   
    233. 建立一个基于对话框的工程MultiThread3,在对话框IDD_MULTITHREAD3_DIALOG中加入一个编辑框 IDC_MILLISECOND,一个按钮IDC_START,标题为“开始” ,一个进度条IDC_PROGRESS1;   
    234.   
    235. 打开 ClassWizard,为编辑框IDC_MILLISECOND添加int型变量m_nMilliSecond,为进度条IDC_PROGRESS1添加CProgressCtrl型变量m_ctrlProgress;   
    236.   
    237. 在MultiThread3Dlg.h文件中添加一个结构的定义: struct threadInfo  
    238.   
    239. {  
    240.   
    241. UINT nMilliSecond;  
    242.   
    243. CProgressCtrl* pctrlProgress;  
    244.   
    245. };  
    246.   
    247. 线程函数的声明: UINT ThreadFunc(LPVOID lpParam);  
    248.   
    249. 注意,二者应在类CMultiThread3Dlg的外部。   
    250.   
    251. 在类CMultiThread3Dlg内部添加protected型变量: HANDLE hThread;  
    252.   
    253. DWORD ThreadID;  
    254.   
    255. 分别代表线程的句柄和ID。   
    256.   
    257. 在MultiThread3Dlg.cpp文件中进行如下操作:  
    258.   
    259. 定义公共变量 threadInfo Info;  
    260.   
    261. 双击按钮IDC_START,添加相应消息处理函数:void CMultiThread3Dlg::OnStart()   
    262.   
    263. {  
    264.   
    265. // TODO: Add your control notification handler code here  
    266.   
    267. UpdateData(TRUE);  
    268.   
    269. Info.nMilliSecond=m_nMilliSecond;  
    270.   
    271. Info.pctrlProgress=&m_ctrlProgress;  
    272.   
    273. hThread=CreateThread(NULL,  
    274.   
    275. 0,  
    276.   
    277. (LPTHREAD_START_ROUTINE)ThreadFunc,  
    278.   
    279. &Info,  
    280.   
    281. 0,  
    282.   
    283. &ThreadID);  
    284.   
    285. /* 
    286.  
    287. GetDlgItem(IDC_START)->EnableWindow(FALSE); 
    288.  
    289. WaitForSingleObject(hThread,INFINITE); 
    290.  
    291. GetDlgItem(IDC_START)->EnableWindow(TRUE); 
    292.  
    293. */  
    294.   
    295. }  
    296.   
    297. 在函数BOOL CMultiThread3Dlg::OnInitDialog()中添加语句: {  
    298.   
    299. ……  
    300.   
    301. // TODO: Add extra initialization here  
    302.   
    303. m_ctrlProgress.SetRange(0,99);  
    304.   
    305. m_nMilliSecond=10;  
    306.   
    307. UpdateData(FALSE);  
    308.   
    309. return TRUE; // return TRUE unless you set the focus to a control  
    310.   
    311. }  
    312.   
    313. 添加线程处理函数:UINT ThreadFunc(LPVOID lpParam) {  
    314.   
    315. threadInfo* pInfo=(threadInfo*)lpParam;  
    316.   
    317. for(int i=0;i<100;i++)  
    318.   
    319. {  
    320.   
    321. int nTemp=pInfo->nMilliSecond;  
    322.   
    323. pInfo->pctrlProgress->SetPos(i);  
    324.   
    325. Sleep(nTemp);  
    326.   
    327. }  
    328.   
    329. return 0;  
    330.   
    331. }  
    332.   
    333.   顺便补充一点,如果你在void CMultiThread3Dlg::OnStart() 函数中添加/* */语句,编译运行你就会发现进度条不进行刷新,主线程也停止了反应。什么原因呢?这是因为WaitForSingleObject函数等待子线程(ThreadFunc)结束时,导致了线程死锁。因为WaitForSingleObject函数会将主线程挂起(任何消息都得不到处理),而子线程ThreadFunc正在设置进度条,一直在等待主线程将刷新消息处理完毕返回才会检测通知事件。这样两个线程都在互相等待,死锁发生了,编程时应注意避免。   
    334.   

    1. 例程 4 MultiThread4  
    1.   
    2. 该例程测试在Windows下最多可创建线程的数目。   
    3.   
    4. 建立一个基于对话框的工程MultiThread4,在对话框IDD_MULTITHREAD4_DIALOG中加入一个按钮 IDC_TEST和一个编辑框IDC_COUNT,按钮标题为“测试” , 编辑框属性选中Read-only;   
    5.   
    6. 在 MultiThread4Dlg.cpp文件中进行如下操作:  
    7.   
    8. 添加公共变量volatile BOOL m_bRunFlag=TRUE;   
    9.   
    10. 该变量表示是否还能继续创建线程。  
    11.   
    12. 添加线程函数:   
    13.   
    14. DWORD WINAPI threadFunc(LPVOID threadNum)  
    15.   
    16. {  
    17.   
    18. while(m_bRunFlag)  
    19.   
    20. {  
    21.   
    22. Sleep(3000);  
    23.   
    24. }  
    25.   
    26. return 0;  
    27.   
    28. }  
    29.   
    30. 只要 m_bRunFlag 变量为TRUE,线程一直运行。  
    31.   
    32. 双击按钮IDC_TEST,添加其响应消息函数:void CMultiThread4Dlg::OnTest()   
    33.   
    34. {  
    35.   
    36. DWORD threadID;  
    37.   
    38. GetDlgItem(IDC_TEST)->EnableWindow(FALSE);  
    39.   
    40. long nCount=0;  
    41.   
    42. while(m_bRunFlag)  
    43.   
    44. {  
    45.   
    46. if(CreateThread(NULL,0,threadFunc,NULL,0,&threadID)==NULL)  
    47.   
    48. {  
    49.   
    50. m_bRunFlag=FALSE;  
    51.   
    52. break;  
    53.   
    54. }  
    55.   
    56. else  
    57.   
    58. {  
    59.   
    60. nCount++;  
    61.   
    62. }  
    63.   
    64. }  
    65.   
    66. // 不断创建线程,直到再不能创建为止  
    67.   
    68. m_nCount=nCount;  
    69.   
    70. UpdateData(FALSE);  
    71.   
    72. Sleep(5000);  
    73.   
    74. // 延时5秒,等待所有创建的线程结束  
    75.   
    76. GetDlgItem(IDC_TEST)->EnableWindow(TRUE);  
    77.   
    78. m_bRunFlag=TRUE;  
    79.   
    80. }  
    81.   
    82. 五、MFC对多线程编程的支持  
    83.   
    84.   MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。  
    85.   
    86.   工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。  
    87.   
    88.   在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。两种重载函数原型和参数分别说明如下:   
    89.   
    90. (1) CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,  
    91.   
    92. LPVOID pParam,  
    93.   
    94. nPriority=THREAD_PRIORITY_NORMAL,  
    95.   
    96. UINT nStackSize=0,  
    97.   
    98. DWORD dwCreateFlags=0,  
    99.   
    100. LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);  
    101.   
    102. PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下: UINT ExecutingFunction(LPVOID pParam);  
    103.   
    104. 请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。   
    105.   
    106. pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;   
    107.   
    108. nPriority:线程的优先级。如果为0,则线程与其父线程具有相同的优先级;   
    109.   
    110. nStackSize:线程为自己分配堆栈的大小,其单位为字节。如果 nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;   
    111.   
    112. dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;   
    113.   
    114. lpSecurityAttrs:线程的安全属性指针,一般为 NULL;   
    115.   
    116. (2) CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,  
    117.   
    118. int nPriority=THREAD_PRIORITY_NORMAL,  
    119.   
    120. UINT nStackSize=0,  
    121.   
    122. DWORD dwCreateFlags=0,  
    123.   
    124. LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);  
    125.   
    126.   pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,在以后的例子中我们将发现同主线程的机制几乎一样。  
    127.   
    128. 下面我们对CWinThread类的数据成员及常用函数进行简要说明。   
    129.   
    130. m_hThread:当前线程的句柄;   
    131.   
    132. m_nThreadID:当前线程的ID;   
    133.   
    134. m_pMainWnd:指向应用程序主窗口的指针   
    135.   
    136. BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,  
    137.   
    138. UINT nStackSize=0,  
    139.   
    140. LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);  
    141.   
    142.   该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数和API函数CreateThread中的对应参数有相同含义,该函数执行成功,返回非0值,否则返回0。  
    143.   
    144.   一般情况下,调用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。   
    145.   
    146. virtual BOOL CWinThread::InitInstance();  
    147.   
    148.   重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,否则返回0。用户界面线程经常重载该函数,工作者线程一般不使用 InitInstance()。 virtual int CWinThread::ExitInstance();  
    149.   
    150.   在线程终结前重载该函数进行一些必要的清理工作。该函数返回线程的退出码,0表示执行成功,非0值用来标识各种错误。同 InitInstance()成员函数一样,该函数也只适用于用户界面线程。   
    151.   
    152. 六、MFC多线程编程实例  
    153.   
    154.   在Visual C++ 6.0编程环境中,我们既可以编写C风格的32位Win32应用程序,也可以利用MFC类库编写C++风格的应用程序,二者各有其优缺点。基于Win32的应用程序执行代码小巧,运行效率高,但要求程序员编写的代码较多,且需要管理系统提供给程序的所有资源;而基于 MFC类库的应用程序可以快速建立起应用程序,类库为程序员提供了大量的封装类,而且Developer Studio为程序员提供了一些工具来管理用户源程序,其缺点是类库代码很庞大。由于使用类库所带来的快速、简捷和功能强大等优越性,因此除非有特殊的需要,否则Visual C++推荐使用MFC类库进行程序开发。  
    155.   
    156. 我们知道,MFC中的线程分为两种:用户界面线程和工作者线程。我们将分别举例说明。  
    157.   
    158. 用 MFC 类库编程实现工作者线程  
    159.   
    160. 例程5 MultiThread5  
    161.   
    162. 为了与Win32 API对照,我们使用MFC 类库编程实现例程3 MultiThread3。  
    163.   
    164. 建立一个基于对话框的工程MultiThread5,在对话框IDD_MULTITHREAD5_DIALOG中加入一个编辑框 IDC_MILLISECOND,一个按钮IDC_START,标题为“开始” ,一个进度条IDC_PROGRESS1;   
    165.   
    166. 打开 ClassWizard,为编辑框IDC_MILLISECOND添加int型变量m_nMilliSecond,为进度条IDC_PROGRESS1添加CProgressCtrl型变量m_ctrlProgress;   
    167.   
    168. 在MultiThread5Dlg.h文件中添加一个结构的定义: struct threadInfo  
    169.   
    170. {  
    171.   
    172. UINT nMilliSecond;  
    173.   
    174. CProgressCtrl* pctrlProgress;  
    175.   
    176. };  
    177.   
    178. 线程函数的声明:UINT ThreadFunc(LPVOID lpParam);   
    179.   
    180. 注意,二者应在类CMultiThread5Dlg 的外部。  
    181.   
    182. 在类CMultiThread5Dlg内部添加protected型变量:  
    183.   
    184. CWinThread* pThread;   
    185.   
    186. 在MultiThread5Dlg.cpp文件中进行如下操作:定义公共变量:threadInfo Info;   
    187.   
    188. 双击按钮IDC_START,添加相应消息处理函数:  
    189.   
    190. void CMultiThread5Dlg::OnStart()   
    191.   
    192. {  
    193.   
    194. // TODO: Add your control notification handler code here  
    195.   
    196. UpdateData(TRUE);  
    197.   
    198. Info.nMilliSecond=m_nMilliSecond;  
    199.   
    200. Info.pctrlProgress=&m_ctrlProgress;  
    201.   
    202. pThread=AfxBeginThread(ThreadFunc,  
    203.   
    204. &Info);  
    205.   
    206. }  
    207.   
    208. 在函数BOOL CMultiThread3Dlg::OnInitDialog()中添加语句: {  
    209.   
    210. ……  
    211.   
    212. // TODO: Add extra initialization here  
    213.   
    214. m_ctrlProgress.SetRange(0,99);  
    215.   
    216. m_nMilliSecond=10;  
    217.   
    218. UpdateData(FALSE);  
    219.   
    220. return TRUE; // return TRUE unless you set the focus to a control  
    221.   
    222. }  
    223.   
    224. 添加线程处理函数: UINT ThreadFunc(LPVOID lpParam)  
    225.   
    226. {  
    227.   
    228. threadInfo* pInfo=(threadInfo*)lpParam;  
    229.   
    230. for(int i=0;i<100;i++)  
    231.   
    232. {  
    233.   
    234. int nTemp=pInfo->nMilliSecond;  
    235.   
    236. pInfo->pctrlProgress->SetPos(i);  
    237.   
    238. Sleep(nTemp);  
    239.   
    240. }  
    241.   
    242. return 0;  
    243.   
    244. }  
    245.   
    246. 用 MFC 类库编程实现用户界面线程  
    247.   
    248. 创建用户界面线程的步骤:  
    249.   
    250. 使用ClassWizard创建类CWinThread的派生类(以CUIThread类为例) class CUIThread : public CWinThread  
    251.   
    252. {  
    253.   
    254. DECLARE_DYNCREATE(CUIThread)  
    255.   
    256. protected:  
    257.   
    258. CUIThread(); // protected constructor used by dynamic creation  
    259.   
    260. // Attributes  
    261.   
    262. public:  
    263.   
    264. // Operations  
    265.   
    266. public:  
    267.   
    268. // Overrides  
    269.   
    270. // ClassWizard generated virtual function overrides  
    271.   
    272. //{{AFX_VIRTUAL(CUIThread)  
    273.   
    274. public:  
    275.   
    276. virtual BOOL InitInstance();  
    277.   
    278. virtual int ExitInstance();  
    279.   
    280. //}}AFX_VIRTUAL  
    281.   
    282. // Implementation  
    283.   
    284. protected:  
    285.   
    286. virtual ~CUIThread();  
    287.   
    288. // Generated message map functions  
    289.   
    290. //{{AFX_MSG(CUIThread)  
    291.   
    292. // NOTE - the ClassWizard will add and remove member functions here.  
    293.   
    294. //}}AFX_MSG  
    295.   
    296. DECLARE_MESSAGE_MAP()  
    297.   
    298. };  
    299.   
    300. 重载函数InitInstance()和ExitInstance()。 BOOL CUIThread::InitInstance()  
    301.   
    302. {  
    303.   
    304. CFrameWnd* wnd=new CFrameWnd;  
    305.   
    306. wnd->Create(NULL,"UI Thread Window");  
    307.   
    308. wnd->ShowWindow(SW_SHOW);  
    309.   
    310. wnd->UpdateWindow();  
    311.   
    312. m_pMainWnd=wnd;  
    313.   
    314. return TRUE;  
    315.   
    316. }  
    317.   
    318. 创建新的用户界面线程 void CUIThreadDlg::OnButton1()   
    319.   
    320. {  
    321.   
    322. CUIThread* pThread=new CUIThread();  
    323.   
    324. pThread->CreateThread();  
    325.   
    326. }  
    327.   
    328. 请注意以下两点:  
    329.   
    330. A、在UIThreadDlg.cpp的开头加入语句: #include "UIThread.h"  
    331.   
    332. B、把UIThread.h中类 CUIThread()的构造函数的特性由 protected 改为 public。   
    333.   
    334.   用户界面线程的执行次序与应用程序主线程相同,首先调用用户界面线程类的InitInstance()函数,如果返回TRUE,继续调用线程的Run()函数,该函数的作用是运行一个标准的消息循环,并且当收到WM_QUIT消息后中断,在消息循环过程中,Run()函数检测到线程空闲时(没有消息),也将调用OnIdle()函数,最后Run()函数返回,MFC调用ExitInstance()函数清理资源。  
    335.   
    336.   你可以创建一个没有界面而有消息循环的线程,例如:你可以从CWinThread 派生一个新类,在InitInstance函数中完成某项任务并返回FALSE,这表示仅执行InitInstance函数中的任务而不执行消息循环,你可以通过这种方法,完成一个工作者线程的功能。   
    337.   
    338. 例程6 MultiThread6  
    339.   
    340. 建立一个基于对话框的工程MultiThread6,在对话框IDD_MULTITHREAD6_DIALOG中加入一个按钮 IDC_UI_THREAD,标题为“用户界面线程”   
    341.   
    342. 右击工程并选中“New Class…”为工程添加基类为CWinThread派生线程类 CUIThread。   
    343.   
    344. 给工程添加新对话框IDD_UITHREADDLG,标题为“线程对话框”。   
    345.   
    346. 为对话框 IDD_UITHREADDLG创建一个基于CDialog的类CUIThreadDlg。使用ClassWizard为CUIThreadDlg类添加 WM_LBUTTONDOWN消息的处理函数OnLButtonDown,如下: void CUIThreadDlg::OnLButtonDown(UINT nFlags, CPoint point)   
    347.   
    348. {  
    349.   
    350. AfxMessageBox("You Clicked The Left Button!");  
    351.   
    352. CDialog::OnLButtonDown(nFlags, point);  
    353.   
    354. }  
    355.   
    356. 在 UIThread.h中添加 #include "UIThreadDlg.h"  
    357.   
    358. 并在CUIThread类中添加protected变量 CUIThread m_dlg: class CUIThread : public CWinThread  
    359.   
    360. {  
    361.   
    362. DECLARE_DYNCREATE(CUIThread)  
    363.   
    364. protected:  
    365.   
    366. CUIThread(); // protected constructor used by dynamic creation  
    367.   
    368. // Attributes  
    369.   
    370. public:  
    371.   
    372. // Operations  
    373.   
    374. public:  
    375.   
    376. // Overrides  
    377.   
    378. // ClassWizard generated virtual function overrides  
    379.   
    380. //{{AFX_VIRTUAL(CUIThread)  
    381.   
    382. public:  
    383.   
    384. virtual BOOL InitInstance();  
    385.   
    386. virtual int ExitInstance();  
    387.   
    388. //}}AFX_VIRTUAL  
    389.   
    390. // Implementation  
    391.   
    392. protected:  
    393.   
    394. CUIThreadDlg m_dlg;  
    395.   
    396. virtual ~CUIThread();  
    397.   
    398. // Generated message map functions  
    399.   
    400. //{{AFX_MSG(CUIThread)  
    401.   
    402. // NOTE - the ClassWizard will add and remove member functions here.  
    403.   
    404. //}}AFX_MSG  
    405.   
    406. DECLARE_MESSAGE_MAP()  
    407.   
    408. };  
    409.   
    410. 分别重载InitInstance()函数和ExitInstance()函数: BOOL CUIThread::InitInstance()  
    411.   
    412. {  
    413.   
    414. m_dlg.Create(IDD_UITHREADDLG);  
    415.   
    416. m_dlg.ShowWindow(SW_SHOW);  
    417.   
    418. m_pMainWnd=&m_dlg;  
    419.   
    420. return TRUE;  
    421.   
    422. }  
    423.   
    424. int CUIThread::ExitInstance()  
    425.   
    426. {  
    427.   
    428. m_dlg.DestroyWindow();  
    429.   
    430. return CWinThread::ExitInstance();  
    431.   
    432. }  
    433.   
    434. 双击按钮IDC_UI_THREAD,添加消息响应函数: void CMultiThread6Dlg::OnUiThread()   
    435.   
    436. {  
    437.   
    438. CWinThread *pThread=AfxBeginThread(RUNTIME_CLASS(CUIThread));  
    439.   
    440. }  
    441.   
    442. 并在MultiThread6Dlg.cpp的开头添加: #include "UIThread.h"  
    443.   
    444.   好了,编译并运行程序吧。每单击一次“用户界面线程”按钮,都会弹出一个线程对话框,在任何一个线程对话框内按下鼠标左键,都会弹出一个消息框。  
    445.   
    446. 七、线程间通讯  
    447.   
    448.   一般而言,应用程序中的一个次要线程总是为主线程执行特定的任务,这样,主线程和次要线程间必定有一个信息传递的渠道,也就是主线程和次要线程间要进行通信。这种线程间的通信不但是难以避免的,而且在多线程编程中也是复杂和频繁的,下面将进行说明。   
    449.   
    450. 使用全局变量进行通信  
    451.   
    452. 由于属于同一个进程的各个线程共享操作系统分配该进程的资源,故解决线程间通信最简单的一种方法是使用全局变量。对于标准类型的全局变量,我们建议使用volatile 修饰符,它告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。如果线程间所需传递的信息较复杂,我们可以定义一个结构,通过传递指向该结构的指针进行传递信息。  
    453.   
    454.     
    455.   
    456. 使用自定义消息  
    457.   
    458. 我们可以在一个线程的执行函数中向另一个线程发送自定义的消息来达到通信的目的。一个线程向另外一个线程发送消息是通过操作系统实现的。利用 Windows操作系统的消息驱动机制,当一个线程发出一条消息时,操作系统首先接收到该消息,然后把该消息转发给目标线程,接收消息的线程必须已经建立了消息循环。   
    459.   
    460. 例程7 MultiThread7   
    461.   
    462.   该例程演示了如何使用自定义消息进行线程间通信。首先,主线程向CCalculateThread线程发送消息 WM_CALCULATE,CCalculateThread线程收到消息后进行计算,再向主线程发送WM_DISPLAY消息,主线程收到该消息后显示计算结果。   
    463.   
    464. 建立一个基于对话框的工程MultiThread7,在对话框IDD_MULTITHREAD7_DIALOG中加入三个单选按钮 IDC_RADIO1,IDC_RADIO2,IDC_RADIO3,标题分别为 1+2+3+4+......+10,1+2+3+4+......+50,1+2+3+4+......+100。加入按钮IDC_SUM,标题为“求和”。加入标签框IDC_STATUS,属性选中“边框”;   
    465.   
    466. 在MultiThread7Dlg.h中定义如下变量: protected:  
    467.   
    468. int nAddend;  
    469.   
    470. 代表加数的大小。  
    471.   
    472. 分别双击三个单选按钮,添加消息响应函数:void CMultiThread7Dlg::OnRadio1()   
    473.   
    474. {  
    475.   
    476. nAddend=10;  
    477.   
    478. }  
    479.   
    480. void CMultiThread7Dlg::OnRadio2()   
    481.   
    482. {  
    483.   
    484. nAddend=50;  
    485.   
    486. }  
    487.   
    488. void CMultiThread7Dlg::OnRadio3()   
    489.   
    490. {  
    491.   
    492. nAddend=100;  
    493.   
    494. }  
    495.   
    496. 并在OnInitDialog函数中完成相应的初始化工作: BOOL CMultiThread7Dlg::OnInitDialog()  
    497.   
    498. {  
    499.   
    500. ……  
    501.   
    502. ((CButton*)GetDlgItem(IDC_RADIO1))->SetCheck(TRUE);  
    503.   
    504. nAddend=10;  
    505.   
    506. ……  
    507.   
    508. 在MultiThread7Dlg.h中添加: #include "CalculateThread.h"  
    509.   
    510. #define WM_DISPLAY WM_USER+2  
    511.   
    512. class CMultiThread7Dlg : public CDialog  
    513.   
    514. {  
    515.   
    516. // Construction  
    517.   
    518. public:  
    519.   
    520. CMultiThread7Dlg(CWnd* pParent = NULL); // standard constructor  
    521.   
    522. CCalculateThread* m_pCalculateThread;  
    523.   
    524. ……  
    525.   
    526. protected:  
    527.   
    528. int nAddend;  
    529.   
    530. LRESULT OnDisplay(WPARAM wParam,LPARAM lParam);  
    531.   
    532. ……  
    533.   
    534. 在MultiThread7Dlg.cpp中添加: BEGIN_MESSAGE_MAP(CMultiThread7Dlg, CDialog)  
    535.   
    536. ……  
    537.   
    538. ON_MESSAGE(WM_DISPLAY,OnDisplay)  
    539.   
    540. END_MESSAGE_MAP()  
    541.   
    542. LRESULT CMultiThread7Dlg::OnDisplay(WPARAM wParam,LPARAM lParam)  
    543.   
    544. {  
    545.   
    546. int nTemp=(int)wParam;  
    547.   
    548. SetDlgItemInt(IDC_STATUS,nTemp,FALSE);  
    549.   
    550. return 0;  
    551.   
    552. }  
    553.   
    554. 以上代码使得主线程类CMultiThread7Dlg可以处理WM_DISPLAY消息,即在IDC_STATUS标签框中显示计算结果。   
    555.   
    556. 双击按钮IDC_SUM,添加消息响应函数: void CMultiThread7Dlg::OnSum()   
    557.   
    558. {  
    559.   
    560. m_pCalculateThread=  
    561.   
    562. (CCalculateThread*)AfxBeginThread(RUNTIME_CLASS(CCalculateThread));  
    563.   
    564. Sleep(500);  
    565.   
    566. m_pCalculateThread->PostThreadMessage(WM_CALCULATE,nAddend,NULL);  
    567.   
    568. }  
    569.   
    570. OnSum() 函数的作用是建立CalculateThread线程,延时给该线程发送WM_CALCULATE消息。   
    571.   
    572. 右击工程并选中 “New Class…”为工程添加基类为 CWinThread 派生线程类 CCalculateThread。  
    573.   
    574. 在文件CalculateThread.h 中添加 #define WM_CALCULATE WM_USER+1   
    575.   
    576. class CCalculateThread : public CWinThread  
    577.   
    578. {  
    579.   
    580. ……  
    581.   
    582. protected:  
    583.   
    584. afx_msg LONG OnCalculate(UINT wParam,LONG lParam);  
    585.   
    586. ……  
    587.   
    588. 在文件CalculateThread.cpp中添加 LONG CCalculateThread::OnCalculate(UINT wParam,LONG lParam)  
    589.   
    590. {  
    591.   
    592. int nTmpt=0;  
    593.   
    594. for(int i=0;i<=(int)wParam;i++)  
    595.   
    596. {  
    597.   
    598. nTmpt=nTmpt+i;  
    599.   
    600. }  
    601.   
    602. Sleep(500);  
    603.   
    604. ::PostMessage((HWND)(GetMainWnd()->GetSafeHwnd()),WM_DISPLAY,nTmpt,NULL);  
    605.   
    606. return 0;  
    607.   
    608. }  
    609.   
    610. BEGIN_MESSAGE_MAP(CCalculateThread, CWinThread)  
    611.   
    612. //{{AFX_MSG_MAP(CCalculateThread)  
    613.   
    614. // NOTE - the ClassWizard will add and remove mapping macros here.  
    615.   
    616. //}}AFX_MSG_MAP  
    617.   
    618. ON_THREAD_MESSAGE(WM_CALCULATE,OnCalculate)  
    619.   
    620. // 和主线程对比,注意它们的区别  
    621.   
    622. END_MESSAGE_MAP()  
    623.   
    624. 在CalculateThread.cpp文件的开头添加一条: #include "MultiThread7Dlg.h"  
    625.   
    626.   以上代码为 CCalculateThread 类添加了 WM_CALCULATE 消息,消息的响应函数是 OnCalculate,其功能是根据参数 wParam 的值,进行累加,累加结果在临时变量nTmpt中,延时0.5秒,向主线程发送WM_DISPLAY消息进行显示,nTmpt作为参数传递。   
    627.   
    628. 编译并运行该例程,体会如何在线程间传递消息。   
    629.   
    630. 八、线程的同步  
    631.   
    632.   虽然多线程能给我们带来好处,但是也有不少问题需要解决。例如,对于像磁盘驱动器这样独占性系统资源,由于线程可以执行进程的任何代码段,且线程的运行是由系统调度自动完成的,具有一定的不确定性,因此就有可能出现两个线程同时对磁盘驱动器进行操作,从而出现操作错误;又例如,对于银行系统的计算机来说,可能使用一个线程来更新其用户数据库,而用另外一个线程来读取数据库以响应储户的需要,极有可能读数据库的线程读取的是未完全更新的数据库,因为可能在读的时候只有一部分数据被更新过。  
    633.   
    634.   使隶属于同一进程的各线程协调一致地工作称为线程的同步。MFC提供了多种同步对象,下面我们只介绍最常用的四种:   
    635.   
    636. 临界区(CCriticalSection)   
    637.   
    638. 事件(CEvent)   
    639.   
    640. 互斥量(CMutex)   
    641.   
    642. 信号量(CSemaphore)  
    643.   
    644.     
    645.   
    646. 通过这些类,我们可以比较容易地做到线程同步。   
    647.   
    648. A、使用 CCriticalSection 类   
    649.   
    650.   当多个线程访问一个独占性共享资源时,可以使用“临界区”对象。任一时刻只有一个线程可以拥有临界区对象,拥有临界区的线程可以访问被保护起来的资源或代码段,其他希望进入临界区的线程将被挂起等待,直到拥有临界区的线程放弃临界区时为止,这样就保证了不会在同一时刻出现多个线程访问共享资源。  
    651.   
    652. CCriticalSection类的用法非常简单,步骤如下:  
    653.   
    654.     
    655.   
    656. 定义CCriticalSection类的一个全局对象(以使各个线程均能访问),如 CCriticalSection critical_section;   
    657.   
    658. 在访问需要保护的资源或代码之前,调用 CCriticalSection类的成员Lock()获得临界区对象: critical_section.Lock();  
    659.   
    660. 在线程中调用该函数来使线程获得它所请求的临界区。如果此时没有其它线程占有临界区对象,则调用Lock()的线程获得临界区;否则,线程将被挂起,并放入到一个系统队列中等待,直到当前拥有临界区的线程释放了临界区时为止。   
    661.   
    662. 访问临界区完毕后,使用CCriticalSection的成员函数Unlock()来释放临界区:critical_section.Unlock();  
    663.   
    664. 再通俗一点讲,就是线程A执行到critical_section.Lock();语句时,如果其它线程(B)正在执行 critical_section.Lock();语句后且critical_section. Unlock();语句前的语句时,线程A就会等待,直到线程B执行完critical_section. Unlock();语句,线程A才会继续执行。   
    665.   
    666. 下面再通过一个实例进行演示说明。  
    667.   
    668. 例程8 MultiThread8  
    669.   
    670. 建立一个基于对话框的工程MultiThread8,在对话框IDD_MULTITHREAD8_DIALOG中加入两个按钮和两个编辑框控件,两个按钮的ID分别为IDC_WRITEW和IDC_WRITED,标题分别为“写‘W’”和“写‘D’”;两个编辑框的ID分别为IDC_W和 IDC_D,属性都选中Read-only;   
    671.   
    672. 在MultiThread8Dlg.h文件中声明两个线程函数: UINT WriteW(LPVOID pParam);  
    673.   
    674. UINT WriteD(LPVOID pParam);  
    675.   
    676. 使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD;   
    677.   
    678. 在 MultiThread8Dlg.cpp文件中添加如下内容:  
    679.   
    680. 为了文件中能够正确使用同步类,在文件开头添加:#include "afxmt.h"  
    681.   
    682. 定义临界区和一个字符数组,为了能够在不同线程间使用,定义为全局变量:CCriticalSection critical_section;  
    683.   
    684. char g_Array[10];  
    685.   
    686. 添加线程函数:UINT WriteW(LPVOID pParam)  
    687.   
    688. {  
    689.   
    690. CEdit *pEdit=(CEdit*)pParam;  
    691.   
    692. pEdit->SetWindowText("");  
    693.   
    694. critical_section.Lock();  
    695.   
    696. // 锁定临界区,其它线程遇到critical_section.Lock();语句时要等待  
    697.   
    698. //直至执行 critical_section.Unlock();语句  
    699.   
    700. for(int i=0;i<10;i++)  
    701.   
    702. {  
    703.   
    704. g_Array[i]=''W'';  
    705.   
    706. pEdit->SetWindowText(g_Array);  
    707.   
    708. Sleep(1000);  
    709.   
    710. }  
    711.   
    712. critical_section.Unlock();  
    713.   
    714. return 0;  
    715.   
    716. }  
    717.   
    718. UINT WriteD(LPVOID pParam)  
    719.   
    720. {  
    721.   
    722. CEdit *pEdit=(CEdit*)pParam;  
    723.   
    724. pEdit->SetWindowText("");  
    725.   
    726. critical_section.Lock();  
    727.   
    728. // 锁定临界区,其它线程遇到critical_section.Lock();语句时要等待  
    729.   
    730. //直至执行 critical_section.Unlock();语句  
    731.   
    732. for(int i=0;i<10;i++)  
    733.   
    734. {  
    735.   
    736. g_Array[i]=''D'';  
    737.   
    738. pEdit->SetWindowText(g_Array);  
    739.   
    740. Sleep(1000);  
    741.   
    742. }  
    743.   
    744. critical_section.Unlock();  
    745.   
    746. return 0;  
    747.   
    748. }  
    749.   
    750. 分别双击按钮IDC_WRITEW和IDC_WRITED,添加其响应函数: void CMultiThread8Dlg::OnWritew()   
    751.   
    752. {  
    753.   
    754. CWinThread *pWriteW=AfxBeginThread(WriteW,  
    755.   
    756. &m_ctrlW,  
    757.   
    758. THREAD_PRIORITY_NORMAL,  
    759.   
    760. 0,  
    761.   
    762. CREATE_SUSPENDED);  
    763.   
    764. pWriteW->ResumeThread();  
    765.   
    766. }  
    767.   
    768. void CMultiThread8Dlg::OnWrited()   
    769.   
    770. {  
    771.   
    772. CWinThread *pWriteD=AfxBeginThread(WriteD,  
    773.   
    774. &m_ctrlD,  
    775.   
    776. THREAD_PRIORITY_NORMAL,  
    777.   
    778. 0,  
    779.   
    780. CREATE_SUSPENDED);  
    781.   
    782. pWriteD->ResumeThread();  
    783.   
    784. }  
    785.   
    786. 由于代码较简单,不再详述。编译、运行该例程,您可以连续点击两个按钮,观察体会临界类的作用。   
    787.   
    788. B、使用 CEvent 类   
    789.   
    790.   CEvent 类提供了对事件的支持。事件是一个允许一个线程在某种情况发生时,唤醒另外一个线程的同步对象。例如在某些网络应用程序中,一个线程(记为A)负责监听通讯端口,另外一个线程(记为B)负责更新用户数据。通过使用CEvent 类,线程A可以通知线程B何时更新用户数据。每一个 CEvent 对象可以有两种状态:有信号状态和无信号状态。线程监视位于其中的CEvent 类对象的状态,并在相应的时候采取相应的操作。  
    791.   
    792.   在MFC中,CEvent 类对象有两种类型:人工事件和自动事件。一个自动CEvent 对象在被至少一个线程释放后会自动返回到无信号状态;而人工事件对象获得信号后,释放可利用线程,但直到调用成员函数ReSetEvent()才将其设置为无信号状态。在创建CEvent 类的对象时,默认创建的是自动事件。 CEvent 类的各成员函数的原型和参数说明如下:  
    793.   
    794. 1、CEvent(BOOL bInitiallyOwn=FALSE,  
    795.   
    796. BOOL bManualReset=FALSE,  
    797.   
    798. LPCTSTR lpszName=NULL,  
    799.   
    800. LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);  
    801.   
    802. bInitiallyOwn:指定事件对象初始化状态,TRUE为有信号,FALSE为无信号;   
    803.   
    804. bManualReset:指定要创建的事件是属于人工事件还是自动事件。TRUE为人工事件,FALSE为自动事件;   
    805.   
    806. 后两个参数一般设为NULL,在此不作过多说明。   
    807.   
    808. 2、 BOOL CEvent::SetEvent();  
    809.   
    810.   将 CEvent 类对象的状态设置为有信号状态。如果事件是人工事件,则 CEvent 类对象保持为有信号状态,直到调用成员函数 ResetEvent()将 其重新设为无信号状态时为止。如果CEvent 类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent 类对象由系统自动重置为无信号状态。  
    811.   
    812. 如果该函数执行成功,则返回非零值,否则返回零。 3、BOOL CEvent::ResetEvent();  
    813.   
    814.   该函数将事件的状态设置为无信号状态,并保持该状态直至SetEvent()被调用时为止。由于自动事件是由系统自动重置,故自动事件不需要调用该函数。如果该函数执行成功,返回非零值,否则返回零。我们一般通过调用WaitForSingleObject函数来监视事件状态。前面我们已经介绍了该函数。由于语言描述的原因,CEvent 类的理解确实有些难度,但您只要通过仔细玩味下面例程,多看几遍就可理解。   
    815.   
    816. 例程9 MultiThread9  
    817.   
    818. 建立一个基于对话框的工程MultiThread9,在对话框IDD_MULTITHREAD9_DIALOG中加入一个按钮和两个编辑框控件,按钮的ID为IDC_WRITEW,标题为“写‘W’”;两个编辑框的ID分别为IDC_W和IDC_D,属性都选中Read-only;   
    819.   
    820. 在 MultiThread9Dlg.h文件中声明两个线程函数: UINT WriteW(LPVOID pParam);  
    821.   
    822. UINT WriteD(LPVOID pParam);  
    823.   
    824. 使用ClassWizard分别给IDC_W和IDC_D添加CEdit类变量m_ctrlW和m_ctrlD;   
    825.   
    826. 在 MultiThread9Dlg.cpp文件中添加如下内容:   
    827.   
    828. 为了文件中能够正确使用同步类,在文件开头添加   
    829.   
    830. #include "afxmt.h"  
    831.   
    832. 定义事件对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量。 CEvent eventWriteD;  
    833.   
    834. char g_Array[10];  
    835.   
    836. 添加线程函数: UINT WriteW(LPVOID pParam)  
    837.   
    838. {  
    839.   
    840. CEdit *pEdit=(CEdit*)pParam;  
    841.   
    842. pEdit->SetWindowText("");  
    843.   
    844. for(int i=0;i<10;i++)  
    845.   
    846. {  
    847.   
    848. g_Array[i]=''W'';  
    849.   
    850. pEdit->SetWindowText(g_Array);  
    851.   
    852. Sleep(1000);  
    853.   
    854. }  
    855.   
    856. eventWriteD.SetEvent();  
    857.   
    858. return 0;  
    859.   
    860. }  
    861.   
    862. UINT WriteD(LPVOID pParam)  
    863.   
    864. {  
    865.   
    866. CEdit *pEdit=(CEdit*)pParam;  
    867.   
    868. pEdit->SetWindowText("");  
    869.   
    870. WaitForSingleObject(eventWriteD.m_hObject,INFINITE);  
    871.   
    872. for(int i=0;i<10;i++)  
    873.   
    874. {  
    875.   
    876. g_Array[i]=''D'';  
    877.   
    878. pEdit->SetWindowText(g_Array);  
    879.   
    880. Sleep(1000);  
    881.   
    882. }  
    883.   
    884. return 0;  
    885.   
    886. }  
    887.   
    888.   仔细分析这两个线程函数, 您就会正确理解CEvent 类。线程WriteD执行到 WaitForSingleObject(eventWriteD.m_hObject,INFINITE);处等待,直到事件 eventWriteD为有信号该线程才往下执行,因为eventWriteD对象是自动事件,则当WaitForSingleObject()返回时,系统自动把eventWriteD对象重置为无信号状态。   
    889.   
    890. 双击按钮IDC_WRITEW,添加其响应函数: void CMultiThread9Dlg::OnWritew()   
    891.   
    892. {  
    893.   
    894. CWinThread *pWriteW=AfxBeginThread(WriteW,  
    895.   
    896. &m_ctrlW,  
    897.   
    898. THREAD_PRIORITY_NORMAL,  
    899.   
    900. 0,  
    901.   
    902. CREATE_SUSPENDED);  
    903.   
    904. pWriteW->ResumeThread();  
    905.   
    906. CWinThread *pWriteD=AfxBeginThread(WriteD,  
    907.   
    908. &m_ctrlD,  
    909.   
    910. THREAD_PRIORITY_NORMAL,  
    911.   
    912. 0,  
    913.   
    914. CREATE_SUSPENDED);  
    915.   
    916. pWriteD->ResumeThread();  
    917.   
    918. }  
    919.   
    920. 编译并运行程序,单击“写‘W’”按钮,体会事件对象的作用。   
    921.   
    922. C、使用CMutex 类  
    923.   
    924.   互斥对象与临界区对象很像.互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用。当然,互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。  
    925.   
    926. D、使用CSemaphore 类  
    927.   
    928.   当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。CSemaphore 类的对象保存了对当前访问某一指定资源的线程的计数值,该计数值是当前还可以使用该资源的线程的数目。如果这个计数达到了零,则所有对这个CSemaphore 类对象所控制的资源的访问尝试都被放入到一个队列中等待,直到超时或计数值不为零时为止。一个线程被释放已访问了被保护的资源时,计数值减1;一个线程完成了对被控共享资源的访问时,计数值增1。这个被CSemaphore 类对象所控制的资源可以同时接受访问的最大线程数在该对象的构建函数中指定。  
    929.   
    930. CSemaphore 类的构造函数原型及参数说明如下:   
    931.   
    932. CSemaphore (LONG lInitialCount=1,  
    933.   
    934. LONG lMaxCount=1,  
    935.   
    936. LPCTSTR pstrName=NULL,  
    937.   
    938. LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);  
    939.   
    940. lInitialCount:信号量对象的初始计数值,即可访问线程数目的初始值;   
    941.   
    942. lMaxCount:信号量对象计数值的最大值,该参数决定了同一时刻可访问由信号量保护的资源的线程最大数目;   
    943.   
    944. 后两个参数在同一进程中使用一般为NULL,不作过多讨论;   
    945.   
    946.   在用 CSemaphore 类的构造函数创建信号量对象时要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时,则说明当前占用资源的线程数已经达到了所允许的最大数目,不能再允许其它线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过 ReleaseSemaphore()函数将当前可用资源数加1。  
    947.   
    948. 下面给出一个简单实例来说明 CSemaphore 类的用法。  
    949.   
    950. 例程10 MultiThread10  
    951.   
    952. 建立一个基于对话框的工程MultiThread10,在对话框IDD_MULTITHREAD10_DIALOG中加入一个按钮和三个编辑框控件,按钮的ID为IDC_START,标题为“同时写‘A’、‘B’、‘C’”;三个编辑框的ID分别为IDC_A、IDC_B和IDC_C,属性都选中 Read-only;   
    953.   
    954. 在MultiThread10Dlg.h文件中声明两个线程函数: UINT WriteA(LPVOID pParam);  
    955.   
    956. UINT WriteB(LPVOID pParam);  
    957.   
    958. UINT WriteC(LPVOID pParam);   
    959.   
    960. 使用ClassWizard分别给IDC_A、IDC_B和IDC_C添加CEdit类变量m_ctrlA、m_ctrlB和m_ctrlC;   
    961.   
    962. 在 MultiThread10Dlg.cpp文件中添加如下内容:   
    963.   
    964. 为了文件中能够正确使用同步类,在文件开头添加:  
    965.   
    966. #include "afxmt.h"  
    967.   
    968. 定义信号量对象和一个字符数组,为了能够在不同线程间使用,定义为全局变量:CSemaphore semaphoreWrite(2,2); //资源最多访问线程2个,当前可访问线程数2个   
    969.   
    970. char g_Array[10];   
    971.   
    972. 添加三个线程函数:   
    973.   
    974. UINT WriteA(LPVOID pParam)  
    975.   
    976. {  
    977.   
    978. CEdit *pEdit=(CEdit*)pParam;  
    979.   
    980. pEdit->SetWindowText("");  
    981.   
    982. WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);  
    983.   
    984. CString str;  
    985.   
    986. for(int i=0;i<10;i++)  
    987.   
    988. {  
    989.   
    990. pEdit->GetWindowText(str);  
    991.   
    992. g_Array[i]=''A'';  
    993.   
    994. str=str+g_Array[i];  
    995.   
    996. pEdit->SetWindowText(str);  
    997.   
    998. Sleep(1000);  
    999.   
    1000. }  
    1001.   
    1002. ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);  
    1003.   
    1004. return 0;  
    1005.   
    1006. }  
    1007.   
    1008. UINT WriteB(LPVOID pParam)  
    1009.   
    1010. {  
    1011.   
    1012. CEdit *pEdit=(CEdit*)pParam;  
    1013.   
    1014. pEdit->SetWindowText("");  
    1015.   
    1016. WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);  
    1017.   
    1018. CString str;  
    1019.   
    1020. for(int i=0;i<10;i++)  
    1021.   
    1022. {  
    1023.   
    1024. pEdit->GetWindowText(str);  
    1025.   
    1026. g_Array[i]=''B'';  
    1027.   
    1028. str=str+g_Array[i];  
    1029.   
    1030. pEdit->SetWindowText(str);  
    1031.   
    1032. Sleep(1000);  
    1033.   
    1034. }  
    1035.   
    1036. ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);  
    1037.   
    1038. return 0;  
    1039.   
    1040. }  
    1041.   
    1042. UINT WriteC(LPVOID pParam)  
    1043.   
    1044. {  
    1045.   
    1046. CEdit *pEdit=(CEdit*)pParam;  
    1047.   
    1048. pEdit->SetWindowText("");  
    1049.   
    1050. WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE);  
    1051.   
    1052. for(int i=0;i<10;i++)  
    1053.   
    1054. {  
    1055.   
    1056. g_Array[i]=''C'';  
    1057.   
    1058. pEdit->SetWindowText(g_Array);  
    1059.   
    1060. Sleep(1000);  
    1061.   
    1062. }  
    1063.   
    1064. ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL);  
    1065.   
    1066. return 0;  
    1067.   
    1068. }  
    1069.   
    1070. 这三个线程函数不再多说。在信号量对象有信号的状态下,线程执行到WaitForSingleObject语句处继续执行,同时可用线程数减1;若线程执行到WaitForSingleObject语句时信号量对象无信号,线程就在这里等待,直到信号量对象有信号线程才往下执行。   
    1071.   
    1072. 双击按钮 IDC_START,添加其响应函数: void CMultiThread10Dlg::OnStart()   
    1073.   
    1074. {  
    1075.   
    1076. CWinThread *pWriteA=AfxBeginThread(WriteA,  
    1077.   
    1078. &m_ctrlA,  
    1079.   
    1080. THREAD_PRIORITY_NORMAL,  
    1081.   
    1082. 0,  
    1083.   
    1084. CREATE_SUSPENDED);  
    1085.   
    1086. pWriteA->ResumeThread();  
    1087.   
    1088. CWinThread *pWriteB=AfxBeginThread(WriteB,  
    1089.   
    1090. &m_ctrlB,  
    1091.   
    1092. THREAD_PRIORITY_NORMAL,  
    1093.   
    1094. 0,  
    1095.   
    1096. CREATE_SUSPENDED);  
    1097.   
    1098. pWriteB->ResumeThread();  
    1099.   
    1100. CWinThread *pWriteC=AfxBeginThread(WriteC,  
    1101.   
    1102. &m_ctrlC,  
    1103.   
    1104. THREAD_PRIORITY_NORMAL,  
    1105.   
    1106. 0,  
    1107.   
    1108. CREATE_SUSPENDED);  
    1109.   
    1110. pWriteC->ResumeThread();  
    1111.   
    1112. }  
    展开全文
  • 与异步套接字不同,这个采用了多线程技术,里面也含有VS2008里面解决char*与CString的问题,还有一些与孙鑫老师VC6.0的不同的地方也得到了解决,不懂的同学可以下载学习!
  • 基于MFC开发的串口通信,技术用到API,和多线程开发串口通信
  • 我用MFC VC6编写的一个 TCP 一对多通信的程序,就是服务器端利用多线程技术(不使用Select等任何模型), 能同时接收多个客户端的消息, 其次, 服务器端还能将消息群发给所有已连接的客户端, 实现的基本思路 是将...
  • 该例程是针对MFC多线程编程,具体的code介绍见博客地址:http://blog.csdn.net/u013896064 window线程编程
  • mfc中实现多线程

    2013-08-16 12:26:53
    本代码示例如何在mfc中实现多线程,并配有wordwe文档的详细详细说明
  • 多线程mfc的例程代码

    2011-04-26 20:37:01
    使用多线程编程,以vc++框架中mfc为基础,为初学者的例程分析代码
  • 使用C++编写的基于MFC和UDP通信协议的多线程程序,接收信息的功能单独在一个线程里,避免主界面卡顿,可以在循环接收信息,将信息显示在文本框并写入txt的同时,点击按钮发送输入另一个文本框的信息
  • MFC MultiThread(多线程三个实例)This file contains a summary of what you will find in each of the files that make up your MultiThread11 application.
  • 1.在MFC框架中通过多线程来实现多个客户端同时与服务器建立Socket连接。 2.通过在服务器端维护一张转发表来实现各个客户端之间的通信,比如客户端A要给客户端B发送消息,首先客户端A把消息内容和客户端B的信息发给...
  • Windows MFC下的多线程的基本编程例子,多达10个左右,涉及互斥、消息传递、信号量的使用,适合刚接触线程编程的人。
  • 本程序使用TAB控件生成了3个Dialog,每个dialog上都有一个简单的MFC多线程实例,简单易懂,且有注释,是MFC多线程学习的好帮手,多线程传参数:传递窗口句柄等等
  • 这是用VS2013版本的MFC编写的一个多线程多串口程序源码,多个串口可以同时工作,互不影响
  • MFC多线程、摄像头和视频读取,通过多线程技术实现摄像头和视频的同时播放,
  • 我用MFC VS2010编写的一个 TCP 一对多通信的程序,就是服务器端利用多线程技术(不使用Select等任何模型), 能同时接收多个客户端的消息, 其次, 服务器端还能将消息群发给所有已连接的客户端, 实现的基本思路 是...
  • MFC 多线程

    2016-08-02 10:49:13
    MFC多线程开发示例
  • 基于MFC 的Socket类的多线程文件传输

    热门讨论 2010-04-12 18:37:19
    基于MFC 的CSocket类实现的一个多线程的局域网文件传输,例子比较详细,希望能帮到你。
  • 介绍了多线程创建、停止的完整过程,并附加了完整代码及注意事项避免内存泄露等问题发生
  • MFC多线程例子

    2017-11-22 15:17:05
    MFC多线程例子,允许单开单停。MFC多线程例子,允许单开单停。
  • VS MFC 多线程 定时器

    千次阅读 2019-04-22 15:41:50
    (1条消息)vs2010 mfc c++ 多线程 - cyuyan的专栏 - CSDN博客 https://blog.csdn.net/cyuyan112233/article/details/51475331 2.我在两个图片控件上先是实时视屏的时候发现总是在打开视屏之后,其他的控件与界面都动...

    今天是我在弄实时视屏的过程中遇到了很多问题

    1.首先是多线程的使用,在线程中处理数据
    

    (1条消息)vs2010 mfc c++ 多线程 - cyuyan的专栏 - CSDN博客
    https://blog.csdn.net/cyuyan112233/article/details/51475331

    2.我在两个图片控件上先是实时视屏的时候发现总是在打开视屏之后,其他的控件与界面都动不了了,最后看了下面这篇博客发现是MFC本身设置的问题,在显示时需要使用到定时器.
    

    在mfc中利用opencv打开摄像头并显示在窗口上 - duhuzhen的博客 - CSDN博客
    https://blog.csdn.net/duhuzhen/article/details/53133927

    3.定时器的学习与使用
    

    MFC中OnTimer定时器用法 - 净无邪博客 - CSDN博客
    https://blog.csdn.net/naibozhuan3744/article/details/78554198
    MFC中定时器OnTimer函数的使用 - 心-无可取代 - CSDN博客
    https://blog.csdn.net/qq_33723441/article/details/54599022

    OnTimer()函数用于实现控制功能,主要有三个函数实现的
    SetTimer(窗口句柄,定时器ID ,时间间隔,回调函数)
    (1)SetTimer():设置一个定时器开始执行计时器
    (2)KillTimer():停止计时器
    (3)OnTimer():是计时器所执行的代码

    4.在数据处理时有提示对话框,但是他会柱塞线程,于是需要对话框提示定时消失
    

    (1条消息)MFC之MessageBox用法 - jilong17的专栏 - CSDN博客
    https://blog.csdn.net/jilong17/article/details/6943755

    (1)第一种方法
    

    (1条消息)弹出MessageBox并延迟一段时间后自动关闭的一种方法 - zheqingzheqing的博客 - CSDN博客
    https://blog.csdn.net/zheqingzheqing/article/details/51855274

    (2)第2种方法
    

    (1条消息)定时消失的对话框API函数 – MessageBoxTimeout - Floydwish的博客 - CSDN博客
    https://blog.csdn.net/brunomarss/article/details/53928489

    (3)第3种方法
    

    VC 定时自动关闭MessageBox弹出对话框_天路_新浪博客
    http://blog.sina.com.cn/s/blog_5eea91750100oskt.html

    我使用的是第三种方法,简单方便。
    在需要使用的cpp文件中添加以下代码

    // 添加MessageBoxTimeout支持
    extern "C"
    {
        int WINAPI MessageBoxTimeoutA(IN HWND hWnd, IN LPCSTR lpText, IN LPCSTR lpCaption, IN UINT uType, IN WORD wLanguageId, IN DWORD dwMilliseconds);
        int WINAPI MessageBoxTimeoutW(IN HWND hWnd, IN LPCWSTR lpText, IN LPCWSTR lpCaption, IN UINT uType, IN WORD wLanguageId, IN DWORD dwMilliseconds);
    };
    #ifdef UNICODE
    #define MessageBoxTimeout MessageBoxTimeoutW
    #else
    #define MessageBoxTimeout MessageBoxTimeoutA
    #endif
    

    在需要使用的地方调用函数

    // 设置1000ms自动关闭该提示对话框
    		MessageBoxTimeout(NULL,"检测不到圆","提示",MB_OKCANCEL,0,1000);
    
    展开全文
  • MFC:界面多线程

    2018-01-02 13:56:50
    //==============================线程函数================================// UINT ThreadFunc1(LPVOID pParam) { ThreadTestDlg* pDlg = (ThreadTestDlg*)pParam; CEdit* pEdit = &pDlg->_ctrlEdit1; while ...

    创建一个对话框,添加三个编辑框,如下图所示:


    对话框头文件代码 ThreadTestDlg.h :

    #pragma once
    #include "AFXWIN.H"
    
    #define _32Bit1_ int;
    using _32Bit2_ = int;
    typedef int _32Bit3_;
    
    class ThreadTestDlg : public CDialogEx
    {
    	DECLARE_DYNAMIC(ThreadTestDlg)
    
    public:
    	ThreadTestDlg(CWnd* pParent = NULL);
    	virtual ~ThreadTestDlg();
    
    #ifdef AFX_DESIGN_TIME
    	enum { IDD = IDD_ThreadTest };
    #endif
    
    protected:
    	virtual void DoDataExchange(CDataExchange* pDX);
    
    	DECLARE_MESSAGE_MAP()
    
    	_32Bit2_ _value;
    	CString _sValue;
    
    public:
    	bool _isLoop;
    	std::mutex _mtEdit;
    	CString GetValue();
    
    	CWinThread* _pThread1;
    	CWinThread* _pThread2;
    	CWinThread* _pThread3;
    
    	CEdit _ctrlEdit1;
    	CEdit _ctrlEdit2;
    	CEdit _ctrlEdit3;
    
    public:
    	virtual BOOL OnInitDialog();
    	virtual BOOL DestroyWindow();
    };
    对话框源文件代码 ThreadTestDlg.cpp:
    #include "stdafx.h"
    #include "afxdialogex.h"
    #include "ThreadTestDlg.h"
    
    IMPLEMENT_DYNAMIC(ThreadTestDlg, CDialogEx)
    
    ThreadTestDlg::ThreadTestDlg(CWnd* pParent /*=NULL*/)
    	: CDialogEx(IDD_ThreadTest, pParent)
    {
    	_isLoop = true;
    }
    
    ThreadTestDlg::~ThreadTestDlg()
    {
    }
    
    void ThreadTestDlg::DoDataExchange(CDataExchange* pDX)
    {
    	CDialogEx::DoDataExchange(pDX);
    	DDX_Control(pDX, IDC_EDIT1, _ctrlEdit1);
    	DDX_Control(pDX, IDC_EDIT2, _ctrlEdit2);
    	DDX_Control(pDX, IDC_EDIT3, _ctrlEdit3);
    }
    
    BEGIN_MESSAGE_MAP(ThreadTestDlg, CDialogEx)
    	ON_WM_NCDESTROY()
    END_MESSAGE_MAP()
    
    //#define ASWD
    #ifdef ASWD
    void Func()
    {
    	2018年已到来
    }
    #endif
    
    CString ThreadTestDlg::GetValue()
    {
    	unique_lock<std::mutex> lck(_mtEdit);
    	_sValue.Format(_T("%d"), _value++);
    	lck.unlock();
    	return _sValue;
    }
    
    //==============================线程函数================================//
    UINT ThreadFunc1(LPVOID pParam)
    {
    	ThreadTestDlg* pDlg = (ThreadTestDlg*)pParam;
    	CEdit* pEdit = &pDlg->_ctrlEdit1;
    	while (pDlg->_isLoop)
    	{
    		pEdit->SetWindowText(pDlg->GetValue());
    		Sleep(10);
    	}
    	return 0;
    }
    
    UINT ThreadFunc2(LPVOID pParam)
    {
    	ThreadTestDlg* pDlg = (ThreadTestDlg*)pParam;
    	CEdit* pEdit = &pDlg->_ctrlEdit2;
    	while (pDlg->_isLoop)
    	{
    		pEdit->SetWindowText(pDlg->GetValue());
    		Sleep(10);
    	}
    	return 0;
    }
    
    UINT ThreadFunc3(LPVOID pParam)
    {
    	ThreadTestDlg* pDlg = (ThreadTestDlg*)pParam;
    	CEdit* pEdit = &pDlg->_ctrlEdit3;
    	while (pDlg->_isLoop)
    	{
    		pEdit->SetWindowText(pDlg->GetValue());
    		Sleep(10);
    	}
    	return 0;
    }
    
    //==============================消息处理函数================================//
    BOOL ThreadTestDlg::OnInitDialog()
    {
    	CDialogEx::OnInitDialog();
    
    	_value = 0;
    	_pThread1 = AfxBeginThread(ThreadFunc1, (LPVOID)this);
    	_pThread2 = AfxBeginThread(ThreadFunc2, (LPVOID)this);
    	_pThread3 = AfxBeginThread(ThreadFunc3, (LPVOID)this);
    	//_pThread3 = AfxBeginThread(ThreadFunc3, this, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED);
    
    	return TRUE;
    }
    
    BOOL ThreadTestDlg::DestroyWindow()
    {
    	_isLoop = false;
    	TerminateThread(_pThread1->m_hThread, NULL);
    	TerminateThread(_pThread2->m_hThread, NULL);
    	TerminateThread(_pThread3->m_hThread, NULL);
    	delete _pThread1;
    	delete _pThread2;
    	delete _pThread3;
    
    	return CDialogEx::DestroyWindow();
    }


    展开全文
  • 这个工程简单的演示了如何通过MFC的消息机制来实现多线程间的通信。
  • C++/MFC-多线程绘图

    千次阅读 2017-06-01 21:16:32
    一、坐标系转换 GetWindowRect  1、取得屏幕坐标 ... 4、设计函数DrawRect(UINT id,UINT* width),根据线程时间片和EDIT控件ID位置 绘制矩形 void CDialog_Thread_Priority_test::DrawRect(UINT edt_ID, UINT*
  • MFC中利用多线程实现定时器

    热门讨论 2013-04-26 14:03:32
    MFC中利用多线程实现定时器,对于学习多线程和 ontimer的同学有用
  • MFC GDI多线程绘图

    2017-07-25 15:32:45
    MFC GDI多线程绘图
  • VS MFC多线程

    2020-01-08 20:58:46
    本文基本是参考下面的几个博客写的,但是他们写的时候都是全篇文字,并且有的地方写的不是很...多线程之三:MFC多线程及实例 MFC中有两类线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 41,409
精华内容 16,563
关键字:

多线程mfc