首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > VC/MFC >

MFC多线程编程的疑问!一般人解答不了!解决方法

2012-03-25 
MFC多线程编程的疑问!一般人解答不了!MFC多线程编程的疑问,请高手解答!!!在按钮的单击事件的响应函数中启

MFC多线程编程的疑问!一般人解答不了!
MFC多线程编程的疑问,请高手解答!!!

在按钮的单击事件的响应函数中启动两个相同的线程,请看如下代码:
void   CThreadtestDlg::OnBnClickedButton1()
{  
AfxBeginThread(ThreadProc,m_hWnd,0,0,0,NULL);           //   L0
AfxBeginThread(ThreadProc,m_hWnd,0,0,0,NULL);           //   L1
                  TRACE( "Main   thread   pWnd---   %d\n ",this);
}

UINT   ThreadProc(LPVOID   lpParameter)
{
HWND   hWnd   =   (HWND)lpParameter;
CThreadtestDlg*   pWnd   =   (CThreadtestDlg*)(CWnd::FromHandle(hWnd));
TRACE( "Work   thread   pWnd---   %d\n ",pWnd);

CString   str;
int   i   =   2000;
while(i){
str.Format( "%d ",i);
SetWindowText(hWnd,str);
i--;
}
return   0;
}

第一个问题:
试问,为什么这样会阻塞主线程?
如果   注释掉   L1   只启动一个这样的线程,那么为什么不会阻塞?

第二个问题:
工作者线程中调用   FromHandle,   为什么不能得到和正确的窗口对象地址(TRACE的输出不一样)??

第三个问题:
如果改变行L2为   AfxBeginThread(ThreadProc2,this,0,0,0,NULL),直接传递MFC窗口对象指针,启动一个这样的线程:
UINT   ThreadProc2(LPVOID   lpParameter)
{
CThreadtestDlg*   pWnd   =   (CThreadtestDlg*)lpParameter;
TRACE( "Work   thread   2   pWnd---   %d\n ",pWnd);

CString   str;
int   i   =   1000;
while(i)
{
str.Format( "%d ",i);
pWnd-> SetWindowText(str);
i--;
}
return   0;
}
很多书上都说:一个线程只能访问它所创建的MFC对象!但这样的程序也能正常运行,为什么?
引MSDN:
To   pass   these   objects   from   one   thread   to   another,   always   send   them   as   their   native   HANDLE   type.   Passing   a   C++   wrapper   object   from   one   thread   to   another   will   often   result   in   unexpected   results.
到底怎么样的MFC对象指针不能被在多线程间传递呢?
欢迎各位加入讨论,谢谢!


[解决办法]
沙发
[解决办法]
临时包装对象会在空闲时间删除.在同一函数中多次使用是没用问题的,不同函数中使用先前保存下来的指针就有可能出错,特别是对话框初使化时候存下来的指针.
在MFC层次上的函数使用CWnd对象,而本地Windows代码(API)使用句柄.如:当Windows系统调用一个窗口过程时将传递一个HWND参数,而MFC本身的消息机制使用CWnd类;为了更好更高效的实现,MFC需要与Windows系统合作,将句柄与CWnd对象进行关联---它用CHandleMap完成关联.
CHandleMap有两个CMapPtrToPtr的成员变量:m_permanentMap(永久映射表,程序运行过程中对象/句柄之间的关系),m_temporaryMap(临时映射表,在消息存在的过程中才存在).永久表保存显式创建的CWnd对象,当对象创建时将在永久目录下插入一个条目,在CWnd::OnNcDestrory时删除对应条目.但是Windows有时会传入某些不是由开发人员显式创建的窗口的句柄,此时MFC会分配一个临时对象来包装这个句柄并将它们的映射保存到临时映射表中,这些临时对象会在空闲时间被删除并移走相应的临时映射表条目.类似的MFC对象与Windows句柄的映射表有:
m_pmapHWND: 窗口句柄与CWnd对象
m_pampHMENU: 菜单句柄与CMenu对象
m_pmapHDC: 设备环境句柄与CDC对象
m_pmapHGDIOBJ: GDI句柄与CGDI对象
m_mapHIMAGELIST: 图像链表句柄到CImageList对象

当给定句柄,如HWND时,MFC简单调用CWnd* PASCAL CWnd::FromHandle(HWND hWnd), 此函数内部使用CHandleMap::FromHandle(hWnd)获取相关联的CWnd对象.在CHandleMap::FromHandle(h)内部(源代码在WinHand.cpp),先使用CObject* pObject = LookupPermanent(h); if (pObject != NULL)return pObject; 检查永久表;如永久表中不存在,使用pObject = LookupTemporary(h)) != NULL检查临时表,都不存在时,使用pTemp = = m_pClass-> CreateObject();if (pTemp == NULL)AfxThrowMemoryException();m_temporaryMap.SetAt((LPVOID)h, pTemp);创建临时对象并将其插入到临时表中去,同时返回该临时对象.
void CHandleMap::RemoveHandle(HANDLE h)的注释说明临时对象将在空闲时由OnIdel释放:
// remove only from permanent map -- temporary objects are removed


// at idle in CHandleMap::DeleteTemp, always!
如果想不自动释放临时对象,使用void AFXAPI AfxLockTempMaps()/BOOL AFXAPI AfxUnlockTempMaps(BOOL bDeleteTemps)进行锁定

如果要更透彻的搞清楚这个问题,你看看MFC WINDOWS 程序设计第二版。我也是记住这个规则,传递尽量使用句柄,时间长了也忘了为什么了,大概就时跟临时映射表有关系。
[解决办法]
在线程中应该直播HWND和API。
因为TLS(线程局部存储)的原因CWnd::FromHandle并不安全。
[解决办法]
试问,为什么这样会阻塞主线程?因为CWnd::SendWindowText实际上是调用的API SendMessage, 因此有可能造成阻塞
[解决办法]
大家在一般情况下,两个线程的名称都取不一样。

PF楼主研究式的学习方式,几个问题问的很巧。

呵呵,学习一下。
[解决办法]
第一个问题在启动同名线程时,系统对消息的区分是不是不能实现。

补问一个问题,线程里面调用同名线程会如何?
[解决办法]
哦,确实不准确,呵呵,线程用的少。

我记得,MFC中的工作者线程的创建与WIN32中::CreatThread创建是不同的。它封装了32中的::CreatThread,但它除了启动执行的线程外,还将使用的MFC框架中的内部变量初始化.

两次初始化是否有影响呢
[解决办法]
到底怎么样的MFC对象指针不能被在多线程间传递呢?
===============================================
需要从句柄反查MFC对象时,该对象指针就不能在多线程之间传递,而如果函数是直接使用句柄来调用API,那么该调用就是安全的, 但是既然多线程间传递MFC有那么多不安全的因素,我们完全应该避免它

工作者线程中调用 FromHandle, 为什么不能得到和正确的窗口对象地址(TRACE的输出不一样)??
===============================================
TLS的原因,每个线程有自己的句柄表,所以FromHandle肯定是不一样的


阻塞的那个就不知道了...

[解决办法]
最大的不确定因素就是句柄表
如果你能保证调用实际只使用到MFC对象中的句柄而不需要反查(就像SetWindowText那样),那么在线程中使用MFC指针就是安全的

关于反查的例子我一下还真举不出来,CWnd的AfxWndProc是需要反查的,但我一下也想不出怎么在线程里面触发这个trap
[解决办法]
1:回 "很多书上都说:一个线程只能访问它所创建的MFC对象!但这样的程序也能正常运行,为什么? "
因为你的对话框对象在m_pmapHWND映射表中不是存储在线程本地数据中,而是存储在了应用程序全局的m_pmapHWND中。
2:回 "阻塞主线程 "
就是因为SetWindowText间接的调用了SendMessage,使主线程马上处理你的消息,所以阻塞。
3:回 "工作者线程中调用 FromHandle, 为什么不能得到和正确的窗口对象地址(TRACE的输出不一样)?? "
因为FromHandle会在本地线程的m_pmapHWND查找,所以它实际上它本地线程中的m_pmapHWND为空,实际它创建了一临时对象给你,如果你用FromHandlePermanent,程序则会返回NULL.


[解决办法]
第一个问题:不是特别清楚,我猜测有两点可能:其一,线程函数不是可重入的,其二:只有一个消息队列,两个线程同时对一个消息队列进行操作,有可能会发生死锁这样的ITC问题。
第二个问题:句柄-> MFC CWnd对象的映射表是放在TLS里的,句柄虽然可以跨线程传递,但是用这个句柄合成出来的CWnd对象是不同的。正因为如此MFC不是线程安全的。

第三个问题:不是不能这样做,而是不安全,MFC设计的时候没有考虑这样的情况,用是可以用,但是什么时候出问题无法保证。
[解决办法]
什么也不说了!mark下!
[解决办法]
while(i){
str.Format( "%d ",i);
SetWindowText(hWnd,str);
Sleep(0);//加这个看是否阻塞!
i--;
}
[解决办法]
第1个问题, DentistryDoctor
已经解释了 因为CWnd::SendWindowText实际上是调用的API SendMessage, 因此有可能造成阻塞

第2个问题, FromHandle
Big_Stone() ( ) 好像已经解释了
很可能在主线程中只是临时存储了对话框的hWnd,导致线程1、2中无法反查到绑定CWnd,
另,想到 对话框只是继承了CWnd类,用于线程中是否会导致反查出现问题?
jasonshark(没暑假了...) 也提到和讲解了这个问题

第3个问题,jasonshark(没暑假了...)
已经解释了,没有涉及其它类或者消息映射等高级的MFC CWnd封装的特色,简单调用Cwnd对象里的方法似乎不会由什么问题





[解决办法]
mark
[解决办法]
两个线程同时SendMessage,同时对消息队列进行操作,当然有可能阻塞。


[解决办法]
刚才忍不住做了一下试验,发现并没有楼主说的 "阻塞 "现象,甚至3,4个线程一起SetWindowText都没问题, 其实在Windows核心编程里也讲过,当多个线程向另一个线程中的窗口Send消息时,Windows会把这些消息串行化(关于Send消息是否也有一个 "队列 "这点,众口难调,虽然核心编程的说法是有一个队列),因此不会出现多线程操作消息队列的并发问题 -- 话说回来,如果Windows自己都没把自己的消息处理并发问题解决,那也太难看了

我估计楼主说的 "阻塞 "说的是UI消息的响应变迟钝了,其实这个也不难理解, 因为Send消息可以看做是优先级很高的消息,当有很多线程同时向一个线程的窗口Send消息时,该线程的GetMessage或PeekMessage根本就跳不出来, 楼主可以试试再多开几个线程,看看是不是几乎一个UI消息都取不到了
[解决办法]
SendMessage是需要接收方window消息处理线程处理完成才会返回的,并且好像是由操作系统跳过消息队列,直接调用window proc来完成的
似乎说成“优先级高低”不是太准确

所以多个线程调用SetWindowText势必排队等待那个 window的处理线程,也就意味着基本上都是那个window proc在运行


[解决办法]
关于第2个问题涉及的handle与cwnd的映射,MSDN上的比较确切的解释是如下:

Windows 句柄映射
作为通用规则,线程只能访问它创建的 MFC 对象。这是因为临时和永久性 Windows 句柄映射保留在线程本地存储中,以对它进行保护,确保不能有多个线程同时访问它。例如,辅助线程不能执行计算并调用文档的 UpdateAllViews 成员函数来修改包含新数据视图的窗口。此操作将不会有任何效果,因为从 CWnd 对象到 HWND 的映射是主线程的本地映射。这意味着一个线程可能有从 Windows 句柄到 C++ 对象的映射,但是另一个线程可能会将此句柄映射到其他 C++ 对象。在一个线程内所做的更改将不会反映在另一个线程中。

有几种方式可以避免此问题。首先是将各个句柄(如 HWND)而不是 C++ 对象传递到辅助线程。然后,辅助线程通过调用适当 FromHandle 成员函数将这些对象添加到它的临时映射。还可以通过调用 Attach 将对象添加到线程的永久映射,但只有在保证对象比线程存在的时间长时,才应当进行此操作。

[解决办法]
MARK一下
[解决办法]
第一个问题: 消息是分级别的,sendmessage的最高,大于PostMessage投递的消息,也大于qs_quit等,你对主线程的感觉是通过点击按钮点击主界面之类的操作的得到的,而主线程忙于相应子线程发送的消息(级别比点击操作要高)所以感觉上主线程阻塞,实际上明显主窗口的标题在改变,肯定没有阻塞。
[解决办法]
我一般是这样的,主窗口程序a中,有个onTimer函数,不断等待显示另一个线程的数据,b线程处理数据,处理完成后,通知b可以取,b在一直自动运行,直到被a通知结束。我的是数据采集软件,复杂问题简化才是关键。

热点排行