MFC多线程

1.MFC多线程简介

MFC对多线程进行了一层简单的封装,Visual C++中每个线程都是从CWinThread类继承而来的。每一个应用程序的执行都有一个主线程,这个主线程也是从CWinThread类继承而来的。可以利用CWinThread对象创建应用程序执行的其它线程。

MFC用CWinThread对象来表示所有线程。利用MFC可以创建两种线程,分别称之为工作者线程和用户界面线程。二者的主要区别在于工作者线程没有消息循环,而用户界面线程有自己的消息队列和消息循环。工作者线程没有消息机制,通常用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程一般用于处理独立于其他线程执行之外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址即可启动线程来执行任务。

2.MFC多线程基础

为了深入了解MFC下创建线程的方法,我们先深入学习一下CWinThread类。CWinThread类在MFC类结构中的位置如下图所示:




详细的MFC类结构图请参考MSDN:  http://msdn.microsoft.com/zh-cn/library/ws8s10w4(VS.90).aspx


         在创建一个新的线程时,不必直接创建线程对象,因为线程对象是由全局函数AxCreateThread()自动产生的。只要首先定义一个CWinThread类指针,然后调用全局函数AxCreateThread()来产生一个新的线程对象,并调用线程类的CreateThread()成员函数来具体创建线程,最后将新的线程对象的指针返回。应该将其存在CWinThread变量中,以便能够进一步控制线程。

   AxCreateThread()函数和CWinThread:: CreateThread()函数并不是简单地对_beginthreadex()函数进行了一下封装,它还做了一些应用程序框架所需的内部数据的初始化工作,并保证使用正确的C运行库版本,因为在两个函数一开始就要检查环境参数_MT是否已经定义。而且在创建线程过程中还进行了很多检查和测试,保证能够正确产生新的线程,同时在线程创建失败时正确地释放掉已经分配的资源。

注意事项:

(1)一般情况下,推荐使用AfxBeginThread()来一次性地创建并启动一个线程,但是也可以通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。

(2)MFC支持两类多线程,即工作线程和用户界面线程。用户界面线程经常重载InitInstance()和ExitInstance()函数,用以控制用户界面线程实例的初始化和必要的清理工作。工作者线程一般不使用

3.线程函数

(1)工作线程的线程函数:从AfxBeginThread()函数的参数可以看出:

AFX_THREADPROC pfnThreadProc,//线程函数

 LPVOID pParam,               //传给线程函数的参数

其中AFX_THREADPROC为一个宏,其定义如下:

typedef UINT(AFX_CDECL *AFX_THREADPROC)(LPVOID);

从以上语句,可以得到工作线程的线程函数的形式为:

UINT ThreadFunc(LPVOID pParm);

ThreadFunc()函数应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。

pParam:传递给线程函数的一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略。

(2)用户界面线程的线程函数:从AfxBeginThread()函数的参数可以看出:

CRuntimeClasspThreadClass,//从CWinThread派生的类的RUNTIME_CLASS

即pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被创建的用户界面线程的启动、退出等。

有了上面的基础,下面来学习线程的创建。

4.工作线程的创建

工作线程没有消息机制经常用来完成一些后台工作,如计算、打印、等待、循环等,这样用户就不必因为计算机在从事繁杂而耗时的工作而等待。创建一个工作线程相对比较简单。

我们用一个实例来演示工作线程的创建。查找N(很大)以内的素数,为了演示效果,N的取值很大(比如:10000等),并在主界面实时动态显示处理结果,并显示处理进度等。

先说下素数的定义:素数又称质数,指在一个大于1的自然数中,除了1和此整数自身外,不能被其他自然数整除的数。

一个数 n 如果是素数,那么它的所有的因子不超过sqrt(n)(即n的平方),那么我们可以用这个性质用判断一个数是否是素数。在此不讨论该算法的复杂度,只是演示效果,望大牛们不要拍砖哈。

 

5、总之,线程在MFC中经常被用来相互交互传数据和解析使用

     最常用的就是使用后台的线程来更新前台窗口上现实的数据,这就是两个线程的作用,使得消息在其中可以互相呼应;

     还是要清楚后台线程是没有消息循环的,而窗口线程由于继承了CWnd,所有有自己的消息循环和消息队列,这点是很重要的,当然这样的回调就是为了使得前台显示能够和后台程序同步。


### MFC多线程的使用方法及常见问题解决方案 #### 一、MFC 多线程的基本原则 在基于 Microsoft Foundation Classes (MFC) 的多线程应用程序开发中,应严格遵循以下基本原则以避免运行时错误或不可预期的行为: - **线程边界约束**:不应跨线程传递 MFC 对象。通常情况下,一个线程仅能访问由其自身创建的 MFC 对象[^1]。 - 违反上述规则可能导致断言失败或其他异常行为。 #### 二、常见的 UI 更新问题及其解决办法 当尝试通过后台工作线程更新主线程中的 UI 控件时,可能会遇到 `CWnd::AssertValid()` 函数执行出错的情况。这是由于不同线程间的对象访问冲突所致[^2]。以下是推荐的解决方案: ##### 方法 1: 使用消息队列机制 可以通过向主线程发送自定义消息的方式实现安全的 UI 更新。具体步骤如下: 1. 定义一个自定义的消息 ID; 2. 在工作线程中调用 `PostMessage` 或 `SendMessage` 将数据传递给主线程; 3. 主线程接收到消息后负责实际的 UI 绘制操作。 示例代码展示如何利用消息队列完成异步通信: ```cpp // 工作线程函数 UINT WorkerThread(LPVOID pParam) { CMyApp* pApp = reinterpret_cast<CMyApp*>(pParam); if (!pApp || !AfxGetMainWnd()) { return 0; } // 发送自定义消息通知主线程刷新状态栏文字 AfxGetMainWnd()->PostMessage(WM_USER_UPDATE_UI, WPARAM(0), LPARAM(reinterpret_cast<LPARAM>("Task Completed"))); return 0; } // 主窗口类 OnUserUpdateUi 消息处理器 afx_msg LRESULT CMainFrame::OnUserUpdateUi(WPARAM wParam, LPARAM lParam) { CString strText = reinterpret_cast<LPCSTR>(lParam); SetStatusText(strText); // 假设存在此接口用于设置状态栏文本 return 0; } ``` ##### 方法 2: 利用 `_beginthreadex` 替代 `CreateThread` 尽管两者均可用来启动新线程,但后者未初始化 CRT 库资源,在某些场景下容易引发崩溃等问题[^5]。因此建议优先选用前者作为默认选项。 #### 三、关于 Qt 集成下的特殊注意事项 对于混合架构项目而言(例如同时涉及 MFC 和 Qt),需额外关注两框架间交互带来的兼容性挑战。比如加载动态库期间可能出现内存泄漏或者退出流程受阻现象[^4]。对此可采取措施包括但不限于显式卸载共享库实例以及合理安排析构顺序等手段加以规避。 --- ### 总结 综上所述,针对 MFC 平台上的并发编程实践应当始终牢记核心准则——即杜绝越界存取;与此同时借助成熟的设计模式譬如生产者消费者模型配合条件变量同步原语共同构建健壮可靠的系统结构。至于更复杂的跨平台移植需求,则务必深入理解目标环境特性从而制定针对性策略应对潜在风险因素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值