胶衣裤特种图片:IT 技术联盟--VC++ MFC线程处理概述

来源:百度文库 编辑:偶看新闻 时间:2024/04/27 16:47:20

事实上,Win32 API提供了_beginthreadex函数,可以让用户在底层启动线程。总地来说,Win32 API提供的线程处理的接口,引用较为复杂。因此,在Win32 API的基础上,MFC提供了处理线程的类和函数。其中,MFC提供处理线程的类为CWinThread类。CWinThread类使用线程本地存储来管理在MFC环境中的线程的上下文信息。
一般地说,用户可以直接声明CWinThread对象,但在许多情况下,可以让MFC的全局函数AfxBeginThread来创建CWinThread对象。CWinThread类提供了几个函数来对线程进行操作。例如下面的几个函数。
    CWinThread:: AfxBeginThread函数:用来创建进程。
    CWinThread::CreateThread函数:用来启动新的线程。
    CWinThread::SuspendThread函数:用来挂起线程。
    CWinThread::ResumeThread函数:用来恢复线程的执行。
此外,读者还需知道,MFC中支持两种类型的线程:工作者线程和用户界面线程。下面将为读者详细介绍这两种线程的创建和使用。其区别在于:工作者线程常用于完成不要求用户输入的任务,如耗时计算、后台打印之类的任务。因此,其不需要有界面。工作者线程也适用于等待一个事件的发生。例如,从一个应用程序中接收数据,而不必要求用户等待。
17.2.2  创建工作者线程
工作者线程可以说是并行执行的一个函数,其一般用来完成那些不需要用户输入的后台任务。例如数据库备份功能和网络连接状态监视等功能。
比如,一个收费站的进口只有一个收费窗口在工作。该收费窗口正在处理正常的车辆收费工作,此时来了一个车队。该车队有许多车,需要花费很长时间来完成其收费工作。为了不使其他车辆阻塞,需要另外新开一个收费窗口,专门用于解决该车队的收费。如果把正常收费的窗口看做主线程,这个新开的窗口就是工作者线程,如图17-2所示。
 
图17-2  工作者线程
简单地说,创建一个工作者线程就是实现一个控制函数,并将其地址传给适当形式的AfxBeginThread函数的问题。一般来说,工作者线程形式的AfxBeginThread的声明格式如下:
CWinThread* AFXAPI AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority,
UINT nStackSize,
DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs
);
其中,主要参数说明如下所述。
    pfunThreadProc参数:指定线程的入口函数地址。
    pParam参数:指定传递给线程的参数。
简单地说,前两个参数是控制函数的地址和要传送给控制函数的参数。其余的参数可以指定线程的优先级、栈大小、创建后是立即挂起还是立即运行。最后的参数指定线程的安全属性,其默认值为NULL,即表示该线程将继承调用线程的安全属性。

提示:该函数调用成功的返回值是CWinThread类的指针。可以通过它实现对线程的控制。在线程函数返回时线程将被结束,在线程内部可以利用void AfxEndThread(UINT nExitCode);结束线程。其中,nExitCode为退出码。
工作者线程一旦启动,就开始执行控制函数。线程结束,控制函数也就结束了。线程控制函数的原型如下:
UINT MyControllingFunction(LPVOID pParam);
其中的函数名并不是固定的那个函数名,而是用户自定义的函数名,可以为任何合法的命名。如用户自定义名为MyThread。
例如,下面语句创建了一个工作者线程,其中指定线程的入口函数地址为function函数的地址。
UINT function(LPVOID pParam )
{
  while(true)
  {
    printf("Welcome to 21 Visual C++");
    Sleep(1000);
    printf("\t");
    return 0;
  }
}
AfxBeginThread(function, NULL);                        //用于创建工作者线程

1.创建用户界面线程

与工作者线程不同的是,用户界面线程通常用于处理用户的输入,响应用户产生的消息。为了建立一个用户界面线程,需要从CWinThread引出自定义的类,并将该类的运行时信息传给用户界面形式的AfxBeginThread函数,以便创建用户界面线程。
一般来说,用户界面线程的创建过程需要先从基类CWinThread中派生出用户自定义的新类,再使用AfxBeginThread函数创建线程。
【范例17-1】下面将通过一个实例来讲述如何创建一个用户界面线程。该实例实现创建一个独立于应用程序的窗口,其具体操作步骤如下所述。
  新建一个MFC单文档应用程序。单击【File】|【New】菜单项,打开新建对话框,选择建立【MFC AppWizard[exe]】工程,为工程命名为“创建线程”。在AppWizard的第一步中选中【Single document】,其他步骤按照默认设置,单击【Finish】按钮完成创建。
  添加菜单项。为了演示出该实例创建独立于应用程序的用户界面线程,在该工程中添加新的菜单项“用户界面线程”用于创建和启动线程。具体操作为:
打开工作区的【ResourceView】选项卡,修改MFC应用程序向导自动生成的菜单资源。在原菜单基础上再添加一个弹出式菜单“线程”。在其下面添加菜单项“用户界面线程”,设置其ID为ID_THREAD_GUI,如图17-3所示。
  从CWinThread中派生新类。派生新类的方法很简单,在以前章节中也介绍过。可以使用ClassWizard工具单击【Add Class】|【New】命令,也可以通过打开工作区【ClassView】选项卡。右键单击最顶层类集,在弹出的快捷方式菜单中选择【New Class】菜单项来启动【New Class】对话框。在基类列表框中选择CWindThread类,然后在【Name】文本框中输入派生类名。该实例定义其派生类为MyThread。
单击【OK】按钮后即创建了CWinThread类的派生类MyThread。此时读者在工作台的【FileView】标签中找到MyThread.h文件,打开后可看到如下的类定义。
// MyThread thread
class MyThread : public CWinThread
{
    DECLARE_DYNCREATE(MyThread)
protected:
    MyThread();          
    ……
    DECLARE_MESSAGE_MAP()
};

说明:代码中使用了DECLARE_DYNCREATE宏,使用该宏表明MyThread类具有动态创建的能力。使用DECLARE_MESSAGE_MAP宏表明具有消息映射,可以处理命令消息。
再打开MyThread类的实现文件(MyThread.cpp)。下面列出其中一部分代码。
// MyThread
IMPLEMENT_DYNCREATE(MyThread, CWinThread)

MyThread::MyThread()
{
}
……
BEGIN_MESSAGE_MAP(MyThread, CWinThread)
END_MESSAGE_MAP()

提示:代码中使用了IMPLEMENT_DYNCREATE宏是和头文件中DECLARE_ DYNCREATE宏相对应的,其保证新建的类具有动态创建的能力。而BEGIN_MESSA GE_MAP和END_MESSAGE_MAP宏是和头文件中DECLARE_MESSAGE_MAP宏相对应的,使类具有消息映射机制。
  重载CWinThread的虚函数。基类CWinThread有一些可以重载的虚函数。其中InitInstance函数必须要重载,该函数用于初始化实例。ExitInstance函数一般情况下也应该重载,该函数用于清除实例。而Run函数,如果没有特殊需要,一般不去重载。该函数负责将消息分发出去。重载InitInstance函数如示例代码17-1所示。
示例代码17-1
01    BOOL MyThread::InitInstance()
02    {
03        // TODO:  perform and per-thread initialization here
04        CFrameWnd* pFrameWnd= new CFrameWnd();                    //定义框架对象
05        pFrameWnd->CreateEx(0,AfxRegisterWndClass( CS_HREDRAW|CS_VREDRAW) ,
06                "用户界面线程示例",   
07                WS_OVERLAPPEDWINDOW|WS_VISIBLE,
08                CRect(100,100,400,300),
09                NULL,
10                0);                                             //创建窗口
11        m_pMainWnd=pFrameWnd;
12        pFrameWnd->ShowWindow(SW_SHOW);                        //显示窗口
13        pFrameWnd->UpdateWindow();
14        return TRUE;
15    }
  使用AfxBeginThread函数创建并启动线程对象。在CMyView类中,添加新建菜单项的COMMAND命令消息处理函数 OnThreadGui。添加方法为:右键单击CMyView类,选择【Add Windows Message Handle】菜单项,进入添加消息处理函数对话框。在【Class or Object IDS】下拉列表框中选择菜单项ID_Thread_Gui,事件则选择【COMMAND】并双击。
  单击【OK】按钮添加完成。单击【Edit Existing】按钮进入代码编辑器界面。在该消息处理函数中通过调用AfxBeginThread函数来启动用户界面线程,代码如下:
void CMyView::OnThreadGui()
{
    // TODO: Add your command handler code here
    AfxBeginThread(RUNTIME_CLASS(MyThread));                //启动用户界面线程
}
代码中又用到了AfxBeginThread函数,但是创建工作者线程和创建用户界面线程的AfxBeginThread函数的形式是有一定区别的。在这段代码里,用的是创建用户界面线程的AfxBeginThread函数。

警告:因为在视图类CMyView中用到了新建的线程类MyThread。因此,最后不要忘记在CMyView类中包含MyThread类的头文件。具体代码如下:
#include "MyThread.h"
【运行结果】在Visual C++ 6.0中编译后无错误,使用快捷键【Ctrl+F5】运行该工程。
【代码解析】在上述代码中,第4行首先构造了一个框架窗口对象。然后在第5~10行调用其成员函数CreateEx来产生窗口。最后将窗口显示出来。如果用户界面线程正常执行时,就会产生一个标题为“用户界面线程示例”的窗口。读者可以发现,新建的用户界面线程窗口显示在屏幕上。

说明:需要读者了解的是,这个线程窗口不属于创建线程程序的主框架窗口,而是和主框架窗口(也可以说是主线程窗口)并列的。两者的父窗口都是系统的桌面。查看桌面底部的Windows系统任务栏就会发现,除了创建线程应用程序窗口,也有单独的用户界面线程窗口。
此时,这两个窗口可以并行工作,互不影响。用户可以单独关闭子窗口用户界面线程窗口,就相当于正常退出了该子线程窗口,不会对主线程创建进程窗口造成影响。反过来,如果用户关闭主线程窗口,则用户界面线程窗口也会被迫关闭。这种情况属于用户界面线程非正常退出,会造成内存泄漏。

2.创建工作者线程

工作者线程可以说是并行执行的一个函数,其一般用来完成那些不需要用户输入的后台任务。例如数据库备份功能和网络连接状态监视等功能。
    比如,一个收费站的进口只有一个收费窗口在工作。该收费窗口正在处理正常的车辆收费工作,此时来了一个车队。该车队有许多车,需要花费很长时间来完成其收费工作。为了不使其他车辆阻塞,需要另外新开一个收费窗口,专门用于解决该车队的收费。如果把正常收费的窗口看做主线程,这个新开的窗口就是工作者线程。
简单地说,创建一个工作者线程就是实现一个控制函数,并将其地址传给适当形式的AfxBeginThread函数的问题。一般来说,工作者线程形式的AfxBeginThread的声明格式如下:
CWinThread* AFXAPI AfxBeginThread(
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority,
UINT nStackSize,
DWORD dwCreateFlags,
LPSECURITY_ATTRIBUTES lpSecurityAttrs
);
其中,主要参数说明如下所述。
    pfunThreadProc参数:指定线程的入口函数地址。
    pParam参数:指定传递给线程的参数。
简单地说,前两个参数是控制函数的地址和要传送给控制函数的参数。其余的参数可以指定线程的优先级、栈大小、创建后是立即挂起还是立即运行。最后的参数指定线程的安全属性,其默认值为NULL,即表示该线程将继承调用线程的安全属性。

提示:该函数调用成功的返回值是CWinThread类的指针。可以通过它实现对线程的控制。在线程函数返回时线程将被结束,在线程内部可以利用void AfxEndThread(UINT nExitCode);结束线程。其中,nExitCode为退出码。
工作者线程一旦启动,就开始执行控制函数。线程结束,控制函数也就结束了。线程控制函数的原型如下:
UINT MyControllingFunction(LPVOID pParam);
其中的函数名并不是固定的那个函数名,而是用户自定义的函数名,可以为任何合法的命名。如用户自定义名为MyThread。
例如,下面语句创建了一个工作者线程,其中指定线程的入口函数地址为function函数的地址。
UINT function(LPVOID pParam )
{
  while(true)
  {
    printf("Welcome to 21 Visual C++");
    Sleep(1000);
    printf("\t");
    return 0;
  }
}
AfxBeginThread(function, NULL);                        //用于创建工作者线程