类成员函数创建线程和MFC多线程UI更新问题

来源:互联网 发布:蚁群算法解决什么问题 编辑:程序博客网 时间:2024/04/30 06:30


//创建线程函数CreateThread()原型
HANDLE WINAPI CreateThread(
_In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_ SIZE_T dwStackSize,
_In_ LPTHREAD_START_ROUTINE lpStartAddress,
_In_opt_ LPVOID lpParameter,
_In_ DWORD dwCreationFlags,
_Out_opt_ LPDWORD lpThreadId
);
//其中线程函数的原型,也就是LPTHREAD_START_ROUTINE如下
DWORD WINAPI ThreadProc(\_In\_ LPVOID lpParameter);

今天在写一个MFC程序的时候,想将某个类成员函数作为线程函数创建线程,所以将该成员函数按照上面的形式进行声明和定义,编译的时候出现了“函数不接受2个参数”的错误,才意思到非静态类成员函数中的隐藏this指针。
那么解决方法如下:
1. 用外部函数作为线程函数,如果需要操作类里面的私有成员,也可以用友元函数作为线程函数。
2. 用静态成员函数作为线程函数,但这样线程岂不是只能访问静态成员变量和静态成员函数?那我们可以把对象指针作为线程函数的函数传递过去,实现对非静态成员函数和非静态成员变量的访问。

解决完“函数不接受2个参数”的问题后,成功创建了线程,对于创建的这个线程在这里就称之为“非主线程”。
在非主线程中,我有一些新数据希望在MFC的界面中更新显示,那么我就通过AfxGetApp()->GetMainWnd()依次获取当前程序的CWinApp*指针和主窗口的CWnd*指针。然后通过CWnd*指针去调用UpdateData(FALSE)函数。
结果程序就Assert报错了。

在微软支持中找到如下资料:
原链接:https://support.microsoft.com/en-us/kb/147578
In a multi-threaded application written using MFC, you should not pass MFC objects across thread boundaries. As a general rule, a thread should access only those MFC objects that it creates. Failure to do so may cause run-time problems including assertions or unexpected program behavior.

也就是说,在一个多线程应用程序中,线程只能调用有该线程创建的MFC对象,不能利用指针之类的跨线程去调用其他线程创建的MFC对象。
那就很明显了,我的非主线程不能利用主窗口指针去调用主线程创建的MFC对象的成员函数。那么要如何跨线程让其他线程的MFC对象完成我们所期待的操作呢?那就是利用消息机制。
利用句柄给MFC对象拥有者的线程发送自定义消息,让该线程自己去处理。该对象相应消息执行消息处理函数,那我们可以在该处理函数中加入我们需要的操作即可。

那么为什么MFC会有这种限制呢?这是和线程本地存储(TLS)有关的。

微软支持:
原链接:https://support.microsoft.com/en-us/kb/147578
In a Win32 process, all the threads running in the process address space can view all global and static data. A thread can use thread-local-storage (TLS) to store any thread-specific data.
In a multi-threaded environment because windows are owned by threads, MFC keeps the temporary and permanent window handle map in thread local storage. The same is true for other handle maps like those for GDI objects and device contexts. Keeping the window handle maps in thread local storage ensures protection against simultaneous access by several threads.

就是说,在一个WIN32程序中,所有运行的线程都可以访问进程地址空间中的全局和静态数据,而线程利用TLS存储线程自己的特殊数据。
由于在一个多线程环境中窗口是属于线程的,MFC机制将这些全局和局部的窗口句柄映射放在了TLS中,对于其他句柄映射(例如GDI对象和设备上下文)也是一样存储在TLS中。将这些窗口句柄保持在TLS中来确保多线程中的资源保护。
也是这个原因,我们跨线程使用窗口对象是被不允许的。

顺带提一下,上面我用AfxGetApp()->GetMainWnd()获取主窗口对象指针,再由该指针获得m_hWnd句柄,通过该句柄发送消息,这是可行的。
而在百度百科中,出现以下一段话:

afxgetapp – 取应用程序实例指针
getmainwnd – 取主窗口对象指针
这两个函数可以合成一个: afxgetmainwnd();

这是不正确的。因为在AfxGetMainWnd函数中,是通过调用 AfxGetThread()->GetMainWnd()来获取窗口对象指针的。所以二者是有区别的。而且在多线程环境中,AfxGetThread()是可能返回NULL的,那么AfxGetMainWnd返回的窗口对象指针也就为NULL。

0 0
原创粉丝点击