春暖花开
雪化了,花开了,春天来了
posts - 149,comments - 125,trackbacks - 0

调用SendMessage 产生死锁的问题分析

http://www.cppblog.com/woaidongmao/archive/2008/12/17/69696.html

 

 

()       SendMessage 的工作机制

首先我要先简要的说明一个和这个话题有关系的消息处理机制:

    Window操作系统当中,窗口时属于所在Thread的也就是说 你这个窗口在那个Thread 当中Create 的那么你这个窗口就属于那个Thread。同时窗口的消息处理函数也都会在这个Thread 当中被执行的。(不要问为什么 Window 就是这么设计的 嘿嘿)

 

在讲死锁之前我们先把SendMessage的工作机制搞清楚;

SendMessage 发送出来的消息 到底进入不进入消息队列,有人说进入,有人说不进入,其实都是错误的,确切的说是有时进入,有时不进入。那么什么时候进入,什么时候不进入呢? 我们举一例子来说:假如在 Thread A 中有一个 窗口W1,那么 在 Thread A 中像 W1 SendMessage 一个消息,那么这个消息将不会被放入消息队列,而是直接调用了W1的消息处理函数来直接处理了这个消息。这是不被放入队列的情况;假如现在又多了一个Thread B ,那么在 Thread B 中 像 W1 SendMessage 发送消息 这个时候 W1 将被放入到 Thread A 的消息队列当中,这些Thread A 中的消息循环的GetMessage Get到这个消息 并处理之。 这就是进入消息队列情况;根据在哪里我们来看看我的测试结果:

 

测试1我创建了一个无DOC/View 之支持的单文档工程:

我在CMainFrame添加如下代码:

        BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)

               ON_WM_CREATE()

               ON_WM_SETFOCUS()

               ON_MESSAGE(WM_USER + 100,OnMy)

ON_MESSAGE(WM_USER + 200,OnMy2)

END_MESSAGE_MAP()

 

LRESULT CMainFrame::OnMy(WPARAM wParam,LPARAM lParam)

{

             int i = 0;

             return TRUE;

}

 

LRESULT CMainFrame::OnMy2(WPARAM wParam,LPARAM lParam)

{

             int i = 2;

             return TRUE;

}

然后我再 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) 的最后 加入了一行代码:

SendMessage(WM_USER + 100,0,0);

 

此主题相关图片如下:
clip_image002

然后直接 F5 运行程序 等到 程序停止在断点上,我们看看Call Stack 的调用顺序:


clip_image001
此主题相关图片如下:
clip_image003

 

然后 我又将 SendMessage 改成:

PostMessage(WM_USER + 100,0,0);

然后直接 F5 运行程序 等到 程序停止在断点上,我们再看看Call Stack 的调用顺序:


clip_image001
此主题相关图片如下:
clip_image004

通过这2 Call Stack 大家可以很清楚的看到,执行SendMessage的时候,是直接调用了 AfxWndProcBase 这个 消息处理函数(MFC 通过HOOK 将所有窗口的处理函数都重定向到这个 函数上了,AfxWndProcBase()不明白的自己去看《MFC深入浅出》),大家可以很清楚的看到,在SendMessage AfxWindProcBase 之间根本没有调用CWinApp::Run() ,也就是说从SendMessage 到执行OnMy()根本没有通过程序的主消息循环的GetMessage Run 内部好像用的PeekMessage记不清楚了)取消息。那么有人会问,SendMessage的内部就不会先发消息放入队列再通过GetMesssage把消息取出来了吗?答根本没必要那样做,那是脱裤子放P多此一举。

从这个测试例子的结果我判定SendMessage Thread A 中向 W1

SendMessage 的消息根本不进入消息队列。

测试2那么什么时候进入队列呢我来看看这个例子

沿用上面那个例子的代码我将 OnCreate 中的 SendMessage PostMessage 都删除掉。然后加入如下代码:

//Thread Proc

UINT ThreadProc(LPVOID lParam)

{

             CMainFrame * v_pFrameWnd = (CMainFrame *)lParam;

             if(v_pFrameWnd)

             {

                v_pFrameWnd->SendMessage(WM_USER + 100,0,0);

             }

             return 0;

}

并且 在 OnCreate 种加入如下代码:

AfxBeginThread(ThreadProc,this);

然后F5 运行 等待程序停在断点处,看Call Stack 如下:


clip_image001
此主题相关图片如下:
clip_image005

我们发现这个 Call Stack 就和刚才那个PostMessage Call Stack 是一样的 这个 WM_USER + 100 消息是通过 Run 内部的 GetMessage 取出来的 。所以我断定:

 

Thread B 中向W1 SendMessage 发送消息 ,消息是放入了 Thread A 的消息队列中。由于SendMessage的特性只有当消息被执行完毕才能够返回,所以Thread B 中的SendMessage 要等 Thread A 当中消息执行完毕后才能够返回。

()       SendMessage 产生的 死锁问题

Thread 死锁肯定是发生在2Thread 之间,AB B A,就产生了死锁。大家看了上面测试之后一定会发现,SendMessage 的死锁和上面的第二个例子有关系,也就是 说 通过 Thread B W1 发送消息的时候又可能会产生死锁。

 

 

那么死锁 何时产生呢 ?通过上面的例子我们知道了 如果Thread B W1 SendMessage一个消息,那么 Thread B 的这个SendMessage 就要等 Thread A 的队列中的 消息执行完毕才能够返回,如果在 Thread B SendMessage 的同时 Thread A 等待 Thread B 中的某一处理完毕才能够继续处理消息的话,那么这个时候就发送了死锁。

 

我们继续以测试来说明:

测试3

    首先在 CMainFrame中加入一个 成员变量:m_bThreadExit Public

    我们将 UINT ThreadProc(LPVOID lParam) 加入一样代码如下:

       UINT ThreadProc(LPVOID lParam)

{

          CMainFrame * v_pFrameWnd = (CMainFrame *)lParam;

          if(v_pFrameWnd)

          {

             v_pFrameWnd->SendMessage(WM_USER + 100,0,0);

          }

          v_pFrameWnd->m_bThreadExit = TRUE;

          return 0;

}

然后再 OnCreate 当中添加如下代码:

                             m_bThreadExit = FALSE;

          AfxBeginThread(ThreadProc,this);

 

          while(TRUE)

          {

             if(m_bThreadExit)

                break;

             Sleep(55);

}

 

 OK 编译 F5 运行 发现程序 进入无响应状态,好这时我么让程序 暂停:

看看 2Thread Call Stack 都停在那里了?

Main Thread如下:

 

clip_image001此主题相关图片如下:
clip_image006

在看看 另一个线成:



clip_image001
此主题相关图片如下:
clip_image007

这会 是不是 很明了了

MainThread 停在 循环内 等待 m_bThreadExit True,而 另一个线成 则等待 MainThread 处理完毕 WM_USER + 100 这个消息,结果你等我,我等你,死了。。。。

()       处理办法

1 针对上面的例子 我们 可以通过 把SendMessage 改成 PostMessage 的方法来放弃等待。 这样就解决了

2 有些时候 第1种方法不符合要求比如下面这中情况

UINT ThreadProc(LPVOID lParam)

{

          CMainFrame * v_pFrameWnd = (CMainFrame *)lParam;

          if(v_pFrameWnd)

 

          {

          v_pFrameWnd->SetWindowText("lvyang");

          }

          v_pFrameWnd->m_bThreadExit = TRUE;

          return 0;

}

这里面的CWnd::SetWindowText里面实际上调用的是::SetWindowText 而::SetWindowText 里面有调用 SendMessage 发送一个消息给CWnd 的窗口 ,因为::SetWindowText 内部的我们没有办法来修改,那我只能去修改 MainThread 当中的 While 循环了。

 

那如何修改呢? ThreadProc 当中 SetWindowText之所以被诸塞,就是因为 它向 MainThread SendMessage 的消息没有得到处理,那么我们让他处理的不就OK了吗?好那我们就让他处理,代码如下:

MSG msg;

       while(TRUE)

       {

          if(m_bThreadExit)

             break;

          if(::PeekMessage(&msg,NULL,NULL,NULL,PM_NOREMOVE))

          {

             if(::GetMessage(&msg,NULL,NULL,NULL))

             {

                if(!PreTranslateMessage(&msg))

                {

                   ::TranslateMessage(&msg);

                   ::DispatchMessage(&msg);

                }

             }

          }

          Sleep(55);

}

终于搞完了

posted on 2008-12-19 08:42 Sandy 阅读(996) 评论(2)  编辑 收藏 引用 所属分类: 杂项学习

FeedBack:
# re: 调用SendMessage 产生死锁的问题分析
2009-10-16 20:45 | ahenl
SendMessage不放进队列中的,msdn写得很清楚:

1. 同线程时,立即执行窗口过程:If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine.

2. 不同线程时, 待接收线程从队列中取消息时执行窗口过程,并没有说消息放进队列中,只说明了处理时机: If the specified window was created by a different thread, the system switches to that thread and calls the appropriate window procedure. Messages sent between threads are processed only when the receiving thread executes message retrieval code.  回复  更多评论
  
# re: 调用SendMessage 产生死锁的问题分析

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理