使用+C+++和+MFC+进行多线程编程.pdf
《使用+C+++和+MFC+进行多线程编程.pdf》由会员分享,可在线阅读,更多相关《使用+C+++和+MFC+进行多线程编程.pdf(26页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、使用 C+和 MFC 进行多线程编程 Visual Studio 2005 其他版本 Microsoft 基础类(MFC)库提供多线程应用程序支持。本主题描述进程、线程和 MFC 多线程编程方法。进程是应用程序的执行实例。例如,双击“记事本”图标时,将启动运行“记事本”的进程。线程是进程内的执行路径。启动“记事本”时,操作系统创建进程并开始执行该进程的主线程。此线程终止时,进程也终止。启动代码以函数地址的形式将此主线程提供给操作系统。通常是所提供的 main 函数或 WinMain 函数的地址。如果愿意,可以在应用程序中创建其他线程。如果在处理后台任务或维护任务时不希望用户等待这些任务完成,则
2、可能需要创建其他线程。MFC 应用程序中的所有线程都由 CWinThread 对象表示。大多数情况下,甚至不必显式创建这些对象,而只需调用框架 Helper 函数 AfxBeginThread,该函数将为您创建 CWinThread 对象。MFC 区分两种类型的线程:用户界面线程和辅助线程。用户界面线程通常用于处理用户输入及响应用户生成的事件和消息。辅助线程通常用于完成不需要用户输入的任务(如重新计算)。Win32 API 不区分线程类型;它只需要了解线程的起始地址以开始执行线程。MFC 为用户界面中的事件提供消息泵,从而对用户界面线程进行专门处理。CWinApp 是用户界面线程对象的一个示例
3、,因为它从 CWinThread 派生并对用户生成的事件和消息进行处理。应特别注意以下情况:可能有不止一个线程需要访问同一对象。多线程编程:编程提示 介绍了一些可以避免在这些情况下可能发生的问题的技术。多线程编程:如何使用同步类说明如何使用可用的类从多个线程同步访问一个对象。编写和调试多线程编程本身是一项复杂棘手的任务,因为您必须确保一次只能有一个线程访问对象。多线程编程主题没有讲述多线程编程的基础知识,而只是说明了如何在多线程程序中使用 MFC。Visual C+中包含的多线程 MFC 示例阐释了几种多线程“添加功能”和 MFC 中未包含的 Win32 API,但只是一些入门知识。有关操作系
4、统如何处理进程和线程的更多信息,请参见 Platform SDK 中的进程和线程。多线程处理:创建用户界面线程 Visual Studio 2005 其他版本 用户界面线程通常用于处理用户输入和响应用户事件,这些行为独立于执行该应用程序其他部分的线程。已经创建并启动主应用程序线程(在 CWinApp 导出的类中提供)。本文描述创建其他用户界面线程所需的步骤。创建用户界面线程时,必须首先从 CWinThread 派生类。必须使用 DECLARE_DYNCREATE 和 IMPLEMENT_DYNCREATE 宏声明并实现此类。此类必须重写某些函数,也可以重写其他函数。下表列出了这些函数及其用途。
5、创建用户界面线程时要重写的函数 函数 用途 ExitInstance 线程终止时执行清除。通常重写。InitInstance 执行线程实例初始化。必须重写。OnIdle 执行线程特定的闲置时间处理。通常不重写。PreTranslateMessage 将消息调度到 TranslateMessage 和 DispatchMessage 之前对其进行筛选。通常不重写。ProcessWndProcException 截获由线程的消息和命令处理程序引发的未处理异常。通常不重写。Run 控制线程的函数。包含消息泵。一般不重写。MFC 通过参数重载提供两个版本的 AfxBeginThread:一个用于用户界
6、面线程,另一个用于辅助线程。若要启动用户界面线程,请调用 AfxBeginThread,提供下列信息:从 CWinThread 派生的类的 RUNTIME_CLASS。(可选)所需的优先级级别。默认值为正常优先级。有关可用的优先级级别的更多信息,请参见 Platform SDK 中的 SetThreadPriority。(可选)所需的线程堆栈大小。默认值与创建线程的堆栈大小相同。(可选)CREATE_SUSPENDED,如果希望在挂起状态中创建线程。默认值为 0,即正常启动线程。(可选)所需的安全属性。默认值与父线程具有相同的访问权。有关此安全信息格式的更多信息,请参见 Platform SD
7、K 中的 SECURITY_ATTRIBUTES。AfxBeginThread 为您完成大部分工作。它创建类的新对象、使用您提供的信息初始化该对象并调用 CWinThread:CreateThread 开始执行线程。在整个过程中进行检查,确保假如创建过程的任何部分出现故障,所有对象都能被正确地解除分配。多线程处理:终止线程 Visual Studio 2005 其他版本 通常导致线程终止的两种情况是:控制函数退出或不允许线程完成运行。如果字处理器使用后台打印线程,若成功完成打印,则控制函数将正常终止。但是,如果用户要取消打印,后台打印线程则不得不提前终止。本主题介绍如何实现每一种情况,以及在终
8、止后如何获取线程的退出代码。正常线程终止 过早的线程终止 检索线程的退出代码 正常线程终止 对于辅助线程,正常线程终止很简单:退出控制函数并返回表示终止原因的值。可以使用 AfxEndThread 函数或 return 语句。一般情况下,0 表示成功完成,但这取决于您自己。对于用户界面线程,该过程也很简单:从用户界面线程内调用 Platform SDK 中的 PostQuitMessage。PostQuitMessage 采用的唯一参数是线程的退出代码。对于辅助线程,0 通常表示成功完成。过早的线程终止 过早终止线程几乎一样简单:从线程内调用 AfxEndThread。将所需的退出代码作为唯一
9、参数传递。这将停止执行线程、解除对线程堆栈的分配、分离附加到线程的所有 DLL 并从内存中删除线程对象。必须从要终止的线程内调用 AfxEndThread。如果要从其他线程终止线程,必须设置两个线程间的通信方法。检索线程的退出代码 若要获取辅助线程或用户界面线程的退出代码,请调用 GetExitCodeThread 函数。有关此函数的信息,请参见 Platform SDK。此函数获取线程(存储在 CWinThread 对象的 m_hThread 数据成员中)的句柄和 DWORD 的地址。如果线程仍然是活动的,GetExitCodeThread 将 STILL_ACTIVE 放置在提供的 DWO
10、RD 地址中;否则将退出代码放置在该地址中。检索 CWinThread 对象的退出代码还需要一步。默认情况下,当 CWinThread 线程终止时,删除该线程对象。这意味着不能访问 m_hThread 数据成员,因为 CWinThread 对象不再存在。若要避免出现这种情况,请执行以下操作之一:将 m_bAutoDelete 数据成员设置为 FALSE。这使 CWinThread 对象在线程终止后仍可以继续存在。然后可以在线程终止后,访问 m_hThread 数据成员。但是,如果使用此方法,就得销毁 CWinThread 对象,因为框架不会自动删除该对象。这是首选方法。单独存储线程的句柄。创建
11、线程后,(使用:DuplicateHandle)将其 m_hThread 数据成员复制到其他变量,并通过该变量访问该成员。这样,终止后即会自动删除对象,并且仍然可以找到线程终止的原因。请注意:在可以复制句柄之前,线程不终止。执行此操作的最安全的方式是将 CREATE_SUSPENDED 传递到 AfxBeginThread,存储句柄,然后通过调用 ResumeThread 继续执行线程。任一方法都可以使您确定 CWinThread 对象终止的原因。多线程处理:创建辅助线程 Visual Studio 2005 其他版本 辅助线程通常用于处理后台任务,用户不必等待即可继续使用应用程序。重新计算和
12、后台打印等任务是很好的辅助线程示例。本主题详细介绍创建辅助线程所需的步骤。主题包括:启动线程 实现控制函数 示例 创建辅助线程是一个相对较为简单的任务。只需两步即可以使线程运行:实现控制函数和启动线程。不必从 CWinThread 派生类。如果需要特殊版本的 CWinThread,可以从该类派生,但大多数简单辅助线程都不需要这样做。无需修改即可使用 CWinThread。启动线程 AfxBeginThread 有两个重载版本:一个用于用户界面线程,一个用于辅助线程。若要开始执行辅助线程,请调用 AfxBeginThread,并提供下列信息:控制函数的地址。要传递到控制函数的参数。(可选)所需的
13、线程优先级。默认值为正常优先级。有关可用的优先级级别的更多信息,请参见 Platform SDK 中的 SetThreadPriority。(可选)所需的线程堆栈大小。默认值与创建线程的堆栈大小相同。(可选)CREATE_SUSPENDED,如果希望在挂起状态中创建线程。默认值为 0,即正常启动线程。(可选)所需的安全属性。默认值与父线程具有相同的访问权。有关此安全信息格式的更多信息,请参见 Platform SDK 中的 SECURITY_ATTRIBUTES。AfxBeginThread 为您创建和初始化 CWinThread 对象、启动该对象并返回其地址,以便以后引用。在整个过程中进行检
14、查,确保假如创建过程的任何部分出现故障,所有对象都能被正确地解除分配。实现控制函数 控制函数定义线程。输入此函数后线程开始,此函数退出时线程终止。此函数的原型应为:复制 UINT MyControllingFunction(LPVOID pParam);该参数为单个值。函数在此参数中接收的值是在创建线程对象时传递到构造函数的值。控制函数可以用其选择的任何方式解释此值。它可以视为标量值,或指向包含多个参数的结构的指针,也可以忽略。如果参数引用结构,则既可以使用该结构将数据从调用方传递到线程,也可以用该结构将数据从线程传递回调用方。如果使用此类结构将数据传递回调用方,结果准备就绪时,线程需要通知调
15、用方。有关从辅助线程到调用方进行通信的信息,请参见多线程处理:编程提示。函数终止后,应该返回指示终止原因的 UINT 值。一般情况下,此退出代码为 0 指示成功;若为其他值,则指示不同类型的错误。这只依赖于实现。某些线程可以维护对象的使用计数,并返回该对象的当前使用数。有关应用程序如何检索此值的信息,请参见多线程处理:终止线程。在用 MFC 库编写的多线程程序中有一些操作限制。有关这些限制的说明以及使用线程的其他提示,请参见多线程处理:编程提示。控制函数示例 下面的示例演示如何定义控制函数,以及如何从程序的其他部分使用此函数。复制 UINT MyThreadProc(LPVOID pParam
16、)CMyObject*pObject=(CMyObject*)pParam;if(pObject=NULL|!pObject-IsKindOf(RUNTIME_CLASS(CMyObject)return 1;/if pObject is not valid /do something with pObject return 0;/thread completed successfully /inside a different function in the program.pNewObject=new CMyObject;AfxBeginThread(MyThreadProc,pNewOb
17、ject);多线程处理:如何使用同步类 Visual Studio 2005 其他版本 写入多线程应用程序时,线程间的同步资源访问是一个常见问题。两个或多个线程同时访问同一数据会导致不合需要的、不可预知的结果。例如,一个线程可能正在更新结构的内容,而另一个线程正在读取同一结构的内容。无法得知读取线程将会收到何种数据:旧数据、新写入的数据或两种数据都有。MFC 提供了多个同步和同步访问类以帮助解决此问题。本主题说明了可用的类以及如何在典型的多线程应用程序中使用它们创建线程安全类。典型的多线程应用程序具有代表各个线程间要共享的资源的类。正确设计的完全线程安全类不需要调用任何同步函数。该类的任何事情
18、都在内部处理,使您可以将精力集中于如何更好地使用类,而不是它如何会损坏。创建完全线程安全类的有效技术是将同步类合并到资源类中。将同步类合并到共享类是一个简单的过程。以维护链接的帐户列表的应用程序为例。此应用程序允许在独立的窗口中最多检查三个帐户,但是在任何特定的时间,只能更新一个帐户。更新帐户后,通过网络将更新的数据发送到数据存档。此示例应用程序使用所有这三种类型的同步类。因为它一次最多允许检查三个帐户,所以它使用 CSemaphore 限制对三个视图对象的访问。当试图查看第四个帐户时,应用程序或者等到前三个窗口中有一个关闭,或者该尝试失败。更新帐户时,应用程序使用 CCriticalSect
19、ion 确保一次只更新一个帐户。更新成功后,发出信号 CEvent 以释放等待该事件信号发送的线程。此线程将新数据发送到数据存档。设计线程安全类 若要使类完全线程安全,首先将适当的同步类作为数据成员添加到共享类中。在前面的帐户管理示例中,将 CSemaphore 数据成员添加到视图类,将 CCriticalSection 数据成员添加到链接的列表类,将 CEvent 数据成员添加到数据存储类。下一步,将同步调用添加到修改类中的数据或访问受控资源的所有成员函数中。应该在每个函数中创建 CSingleLock 或 CMultiLock 对象,并调用该对象的 Lock 函数。当锁定对象超出范围并被销
20、毁时,该对象的析构函数调用 Unlock 以释放资源。当然,如果愿意,可直接调用 Unlock。用这种方式设计线程安全类使得在多线程应用程序中使用该类与使用非线程安全类一样容易,但却具有更高的安全级别。将同步对象和同步访问权对象封装到资源的类将提供完全线程安全编程的所有优点,而不会有维护同步代码的缺点。下面的代码示例通过使用在共享资源类和 CSingleLock 对象中声明的数据成员 m_CritSection(CCriticalSection 类型),对此方法进行了说明。通过使用 m_CritSection 对象的地址创建 CSingleLock 对象,来试图同步共享资源(从 CWinThr
21、ead 派生)。试图锁定资源,一旦锁定,即完成了共享对象上的工作。完成工作后,即调用 Unlock 取消锁定资源。复制 CSingleLock singleLock(&m_CritSection);singleLock.Lock();/resource locked/.usage of shared resource.singleLock.Unlock();注意 与其他 MFC 同步类不同的是,CCriticalSection 没有计时锁定请求选项。等待释放线程的时间是无限的。此方法的缺点是类将要比没有添加同步对象的相同类慢一些。而且,如果有一个以上的线程可能删除对象,合并方法不一定始终有效。
22、在这种情况下,最好维持单独同步对象。有关确定在不同情况下使用何种同步类的信息,请参见 多线程编程:何时使用同步类。有关同步的更多信息,请参见“Platform SDK”中的 同步。有关 MFC 中的多线程支持的更多信息,请参见 使用 C+和 MFC 进行多线程编程。多线程处理:何时使用同步类 Visual Studio 2005 其他版本 MFC 提供的多线程类分为两类:同步对象(CSyncObject、CSemaphore、CMutex、CCriticalSection 和 CEvent)和同步访问对象(CMultiLock 和 CSingleLock)。当必须控制对资源的访问以确保资源的完
23、整性时,使用同步类。同步访问类用于获取对这些资源的访问权。本主题介绍各个类的适用情况。若要确定应使用的同步类,请询问以下一系列问题:1.应用程序必须等到发生某事才能访问资源(例如,在将数据写入文件之前,必须先从通信端口接收它)吗?如果是,请使用 CEvent。2.同一应用程序内一个以上的线程可以同时访问此资源(例如,应用程序允许在同一文档上最多同时打开五个带有视图的窗口)吗?如果是,请使用 CSemaphore。3.可以有一个以上的应用程序使用此资源(例如,资源在 DLL 中)吗?如果是,请使用 CMutex。如果不是,请使用 CCriticalSection。从不直接使用 CSyncObje
24、ct。它是其他四个同步类的基类。示例 1:使用三个同步类 以维护链接的帐户列表的应用程序为例。此应用程序允许在独立的窗口中最多检查三个帐户,但是在任何特定的时间,只能更新一个帐户。更新帐户后,通过网络将更新的数据发送到数据存档。此示例应用程序使用所有这三种类型的同步类。因为它一次最多允许检查三个帐户,因此使用 CSemaphore 限制对三个视图对象的访问。当试图查看第四个帐户时,应用程序或者等到前三个窗口中有一个关闭,或者该尝试失败。更新帐户时,应用程序使用 CCriticalSection 确保一次只更新一个帐户。更新成功后,发出信号 CEvent 以释放等待该事件信号发送的线程。此线程将
25、新数据发送到数据存档。示例 2:使用同步访问类 选择要使用的同步访问类更为简单。如果应用程序只与访问单个受控资源有关,请使用 CSingleLock。如果需要访问多个受控资源中的任何一个,则使用 CMultiLock。在示例 1 中,应使用 CSingleLock,因为在每种情况下,任何特定时间都只需要一个资源。有关如何使用同步类的信息,请参见多线程处理:如何使用同步类。有关同步的信息,请参见 Platform SDK 中的同步。有关 MFC 中多线程处理支持的信息,请参见使用 C+和 MFC 进行多线程处理。多线程处理:编程提示 Visual Studio 2005 其他版本 访问数据时,使
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 使用 MFC 进行 多线程 编程
限制150内