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

Yao-debo 2007-07-30 05:48:14
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对象指针不能被在多线程间传递呢?
欢迎各位加入讨论,谢谢!
...全文
2052 31 打赏 收藏 转发到动态 举报
写回复
用AI写文章
31 条回复
切换为时间正序
请发表友善的回复…
发表回复
caitian6 2007-08-10
  • 打赏
  • 举报
回复
mark
Yao-debo 2007-08-04
  • 打赏
  • 举报
回复
谢谢各位的解答!
dabang_007 2007-08-03
  • 打赏
  • 举报
回复
第一个问题: 消息是分级别的,sendmessage的最高,大于PostMessage投递的消息,也大于qs_quit等,你对主线程的感觉是通过点击按钮点击主界面之类的操作的得到的,而主线程忙于相应子线程发送的消息(级别比点击操作要高)所以感觉上主线程阻塞,实际上明显主窗口的标题在改变,肯定没有阻塞。
ediex 2007-08-03
  • 打赏
  • 举报
回复
我一般是这样的,主窗口程序a中,有个onTimer函数,不断等待显示另一个线程的数据,b线程处理数据,处理完成后,通知b可以取,b在一直自动运行,直到被a通知结束。我的是数据采集软件,复杂问题简化才是关键。
monstersky 2007-08-02
  • 打赏
  • 举报
回复
MARK一下
littlegang 2007-08-02
  • 打赏
  • 举报
回复
关于第2个问题涉及的handle与cwnd的映射,MSDN上的比较确切的解释是如下:

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

有几种方式可以避免此问题。首先是将各个句柄(如 HWND)而不是 C++ 对象传递到辅助线程。然后,辅助线程通过调用适当 FromHandle 成员函数将这些对象添加到它的临时映射。还可以通过调用 Attach 将对象添加到线程的永久映射,但只有在保证对象比线程存在的时间长时,才应当进行此操作。
jasonshark 2007-08-01
  • 打赏
  • 举报
回复
刚才忍不住做了一下试验,发现并没有楼主说的"阻塞"现象,甚至3,4个线程一起SetWindowText都没问题, 其实在Windows核心编程里也讲过,当多个线程向另一个线程中的窗口Send消息时,Windows会把这些消息串行化(关于Send消息是否也有一个"队列"这点,众口难调,虽然核心编程的说法是有一个队列),因此不会出现多线程操作消息队列的并发问题 -- 话说回来,如果Windows自己都没把自己的消息处理并发问题解决,那也太难看了

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

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

拿节 2007-07-31
  • 打赏
  • 举报
回复
什么也不说了!mark下!
会思考的草 2007-07-31
  • 打赏
  • 举报
回复
第一个问题:不是特别清楚,我猜测有两点可能:其一,线程函数不是可重入的,其二:只有一个消息队列,两个线程同时对一个消息队列进行操作,有可能会发生死锁这样的ITC问题。
第二个问题:句柄->MFC CWnd对象的映射表是放在TLS里的,句柄虽然可以跨线程传递,但是用这个句柄合成出来的CWnd对象是不同的。正因为如此MFC不是线程安全的。

第三个问题:不是不能这样做,而是不安全,MFC设计的时候没有考虑这样的情况,用是可以用,但是什么时候出问题无法保证。
lyg_zy 2007-07-31
  • 打赏
  • 举报
回复
1:回"很多书上都说:一个线程只能访问它所创建的MFC对象!但这样的程序也能正常运行,为什么?"
因为你的对话框对象在m_pmapHWND映射表中不是存储在线程本地数据中,而是存储在了应用程序全局的m_pmapHWND中。
2:回"阻塞主线程"
就是因为SetWindowText间接的调用了SendMessage,使主线程马上处理你的消息,所以阻塞。
3:回"工作者线程中调用 FromHandle, 为什么不能得到和正确的窗口对象地址(TRACE的输出不一样)??"
因为FromHandle会在本地线程的m_pmapHWND查找,所以它实际上它本地线程中的m_pmapHWND为空,实际它创建了一临时对象给你,如果你用FromHandlePermanent,程序则会返回NULL.

jasonshark 2007-07-31
  • 打赏
  • 举报
回复
最大的不确定因素就是句柄表
如果你能保证调用实际只使用到MFC对象中的句柄而不需要反查(就像SetWindowText那样),那么在线程中使用MFC指针就是安全的

关于反查的例子我一下还真举不出来,CWnd的AfxWndProc是需要反查的,但我一下也想不出怎么在线程里面触发这个trap
Yao-debo 2007-07-31
  • 打赏
  • 举报
回复
第1个问题, DentistryDoctor
已经解释了 因为CWnd::SendWindowText实际上是调用的API SendMessage, 因此有可能造成阻塞
-----------------------------------------------------------------------------------
问题是这样的:启动一个线程确实不阻塞,而两个就阻塞。
Yao-debo 2007-07-31
  • 打赏
  • 举报
回复
楼上的意思是不是,传递MFC对象指针会产生不确定的因素?
这是不是可以解释很多书上在讲解MFC多线程编程时直接传递MFC窗口对象指针呢
继续关注!
lazyter1 2007-07-31
  • 打赏
  • 举报
回复
mark
littlegang 2007-07-31
  • 打赏
  • 举报
回复
第1个问题, DentistryDoctor
已经解释了 因为CWnd::SendWindowText实际上是调用的API SendMessage, 因此有可能造成阻塞

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

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




FFSB 2007-07-31
  • 打赏
  • 举报
回复
while(i){
str.Format("%d",i);
SetWindowText(hWnd,str);
Sleep(0);//加这个看是否阻塞!
i--;
}
jasonshark 2007-07-30
  • 打赏
  • 举报
回复
到底怎么样的MFC对象指针不能被在多线程间传递呢?
===============================================
需要从句柄反查MFC对象时,该对象指针就不能在多线程之间传递,而如果函数是直接使用句柄来调用API,那么该调用就是安全的, 但是既然多线程间传递MFC有那么多不安全的因素,我们完全应该避免它

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


阻塞的那个就不知道了...
fronz 2007-07-30
  • 打赏
  • 举报
回复
哦,确实不准确,呵呵,线程用的少。

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

两次初始化是否有影响呢
加载更多回复(11)

15,471

社区成员

发帖
与我相关
我的任务
社区描述
VC/MFC 进程/线程/DLL
社区管理员
  • 进程/线程/DLL社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧