对话框中WaitForSingleObject等待线程退出导致程序阻塞的原因及解决
????? 在對話框中新建一個線程worker thread,當用戶點擊cancel時,通知該線程函數退出,同時用WaitForSingleObject等待該線程結束。但是每當用戶點擊Cancel后,程序會卡在OnCancel函數中的WaitForSingleObject處,必須要強制結束才能退出。
????? 在網上查了一下,大致原因如下:
?
????? WaitForSingleObject會阻塞對話框線程(Dialog thread),同時也會導致了對話框的消息循環機制被阻塞 ,而我在線程函數中會對對話框有一些UI操作(SetPos, SetWindowText),這些對對話框的UI操作實際上是通過線程向控件發送消息得到的 ( SendMessage(m_hWnd, PBM_SETPOS, nPos, 0L) ),因此WaitForSingleObject阻塞了消息循環,也就導致 SendMessage無法返回,卡住了線程 ,WaitForSingleObject也就無法返回了。
?
??? 具體的修改方法如下:
???? 使用 MsgWaitForMultipleObjects代替 WaitForSingleObject, 這個函數即可以等待信號(thread,event,mutex等等),也可以等待指定類型的消息(MSG),函數聲明如下:
?
?????? DWORD WINAPI MsgWaitForMultipleObjects(
??????? __in???????????????????????? DWORD nCount, //number of objects to be wait
??????? __in???????????????????????? const HANDLE* pHandles, //An array of object handles.
??????? __in???????????????????????? BOOL bWaitAll, //If this parameter is TRUE, the function returns when the states
??????????????????????????????????????????????????????????????? //of all objects in the pHandles array have been set to signaled
??????????????????????????????????????????????????????????????? //and an message has been received.
??????? __in???????????????????????? DWORD dwMilliseconds, //time-out interva
??????? __in???????????????????????? DWORD dwWakeMask //the type of message to be wait
????? );
?
?????? 參數dwWakeMask定義了要等待的消息的類型,根據這個函數,將WaitForSingleObject改寫為:
?
[cpp] view plaincopy print??
?? ?? 其中, PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)和DispatchMessage(&msg)這兩句不能少,如果缺少程序仍然會卡在UI操作(SetPos, SetWindowText)處,由此判定MsgWaitForMultipleObjects實際上也阻塞了消息循環。那么,PeekMessage和 DispatchMessage是怎樣工作,使得消息循環可以正常工作?對此,我有以下兩點猜測:
???? 猜測1: PeekMessage和 DispatchMessage將SetPos中發送的 PBM_SETPOS 消息從隊列中取出并且分發給相應的窗口,即完成了消息處理。為了驗證這個方法,我跟蹤了一下PeekMessage得到的msg,發現并沒有 PBM_SETPOS 消息。這是為什么呢?這是由于SetPos是調用的SendMessage來更新進度條,因此該消息并不進入對話框線程的Message Queue中,所以PeekMessage 并不能得到PBM_SETPOS消息,也就是說被阻塞的 PBM_SETPOS消息并不是通過PeekMessage和 DispatchMessage來處理的。
???? 猜測2: MsgWaitForMultipleObjects函數返回后,由于不再阻塞消息處理,此時在 PeekMessage同時,消息處理函數處理了 PBM_SETPOS消息。如果是這樣的話,我將 PeekMessage和 DispatchMessage改為其它的程序段,比如一些無意義的賦值語句,是否也可以達到相同的效果?實驗后發現并不可行。
??? 用debug跟蹤了一下程序執行的流程,如下:
?
??????????? SetPos(SendMessage) ????????????????? worker thread
????? --> MsgWaitForMultipleObjects返回 ????? Dlg thread
????
????? --> PeekMessage???????????????????????????????????????? Dlg thread
??????
??????? --> SetPos返回 ??????????????????????????????????? Worker thread
????? --> PeekMessage返回 ????????????????????????????????? Dlg threa
?
?
????? 根據上面的流程,合理的解釋應該是:MsgWaitForMultipleObject等待過程中Worker thread仍然被阻塞,當Dialog中有消息到來時MsgWaitForMultipleObject返回,此時調用PeekMessage,消息處理函數被激活,處理 PBM_SETPOS消息,workder thread中的SetPos函數返回。
????? 然而,究竟是怎么樣 PeekMessage會激活消息處理函數?我思考了很久在《windows核心編程》第26章“窗口消息”找到了答案:
?
???? 對于接收消息的線程而言(本文中為 Dlg thread ),如果該線程正在執行代碼,并且沒有等待消息 (如調用GetMessage、PeekMessage或WaitMessage)時,發送過來的消息不會被該線程處理,而發送消息的線程(本文中為worker thread)中的SendMessage函數也不會返回。
?
????? 根據上面的文字,也就是說,在執行 對話框中某個函數時,如果函數沒有退出,并且沒有執行 GetMessage、PeekMessage或WaitMessage函數,那么從別的線程發送來的SendMessage來的消息將不會被處理。此時再返回看看msdn中對PeekMessage的定義,可能會更加清楚:
?
??? ? Dispatches incoming sent messages, checks the thread message queue for a posted message, and retrieves the message (if any exist).
????? During this call, the system delivers pending, nonqueued messages, that is, messages sent to windows owned by the calling thread using the? SendMessage, SendMessageCallback, SendMessageTimeout, or SendNotifyMessage function. Then the first queued message that matches the specified filter is retrieved.
????? 也就是說,PeekMessage這個函數所做的工作不止是從隊列中拿出posted message,還在此之前會先處理nonqueued messages(send message)。
????
????? 找到了send message的處理流程,也就不難解釋上面程序執行的流程了。
????? 還可以看出:MsgWaitForMultipleObjects實際上在這其中并沒有起到什么實質性的作用,只是定期返回而已,那么根據此,可以用固定時間的WaitForSingleObject來代替MsgWaitForMultipleObjects ,并且循環等待 ;同時,我們也沒有必要手動DispatchMessage,只需要PeekMessage即可,需要注意的是如果刪除了 DispatchMessage, 在 PeekMessage時不能將消息從隊列取走。 代碼如下:
[cpp] view plaincopy print?
?
????? 經過測試,代碼運行正確!
????? 當然,由于MsgWaitForMultipleObjects是基于消息驅動的返回,WaitForSingleObject只是普通的定時返回,而本文的情況就是由于消息阻塞造成的,所以MsgWaitForMultipleObjects比WaitForSingleObject在應用時時效性更好,如果改成WaitForSingleObject,在測試中感覺會有略為滯后,所以實際還是以MsgWaitForMultipleObjects為佳。
?
?
?
總結
以上是生活随笔為你收集整理的对话框中WaitForSingleObject等待线程退出导致程序阻塞的原因及解决的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: DOM操作获取标签方法、数据类型
- 下一篇: python标准库os的方法listdi