PostMessage與SendMessage各自的問題


  深入解析SendMessage、PostMessage

    本文將使用C++語言,在MFC框架的配合下給出PostMessage、SendMessage等的使用方式與使用不當造成的后果(討論均針對自定義的消息進行)。如有什么錯誤,歡迎指正。

 

寫過Windows程序的同學都知道PostMessage、SendMessage的區別,PostMessage函數調用發送之后,立即返回,不等待消息處理完成。而SendMessage則讓調用的線程處於阻塞(BLOCk)狀態,直到消息處理完成。

 

       正由於這兩個函數的區別導致了如下想法:

       想法1:PostMessage立即返回,在程序中,處理界面顯示(如處理進度條、滾動條等)時使用PostMessage,不會影響程序的用戶體驗。

       想法2:在程序中全用PostMessage,放棄SendMessage,好處:PostMessage是立即返回的,可以不影響程序的正常流程,就算在消息處理函數中卡死了,也不影響主線程的運行。

 

       起初“學習”到了這些想法,以為受益匪淺,但經過一段時間之后,發現此兩種想法都是不可取的。

 

       分析想法1:

       這里可分為兩點:

1)  在主線程中Post消息,以處理進度條顯示(用WM_MY_TEST的參數WPARAM、LPARAM來處理進度條的顯示)

代碼:code_1

#define WM_MY_TEST (WM_USER + 100)

void CMyDlg::OnBnClickedOk()

{

     int nParam1 = 0;

     int nParam2 = 0;

     for (int nIndex = 0; nIndex < 1000; nIndex++)

     {

         // Do other things

         // …

         nParam1++;

         nParam2++;

         PostMessage(WM_MY_TEST, (WPARAM)&nParam1, (LPARAM)&nParam2);

     }

     //OnOK();

}

 

LRESULT CMyDlg::OnMyTest(WPARAM wParam, LPARAM lParam)

{

     static int nTimes = 0;

     CString strOutPut;

 

     int* pParam1 = (int*)wParam;

     int* pParam2 = (int*)lParam;

     nTimes++;

 

     strOutPut.Format(_T("%s%d   %s%d %s%d"),

         _T("Param1 = "), *pParam1,

         _T("Param2 = "), *pParam2,

         _T("RealTimes = "), nTimes);

 

     OutputDebugString(strOutPut);

     return 0;

}

 

code_1將運行的結果:

Param1 = 0 Param2 = 0 RealTimes = 1

Param1 = 0 Param2 = 0 RealTimes = 2

Param1 = 0 Param2 = 0 RealTimes = 3

Param1 = 0 Param2 = 0 RealTimes = 1000

結果遠不如我們所料,表現為PostMessage多次發送時,2~1000的消息參數全被沖掉了。用pParam1、pParam2來處理進度條的話,后果可想而知(進度條根本沒動)。如果將上面的PostMessage改為SendMessage,結果如下:

Param1 = 1 Param2 = 1 RealTimes = 1

Param1 = 2 Param2 = 2 RealTimes = 2

Param1 = 3 Param2 = 3 RealTimes = 3

Param1 = 1000 Param2 = 1000 RealTimes = 1000

可見,穩定的輸出了需要的內容,可以很好的控制。

在此情況下(主線程中Post消息時),不僅沒有改善用戶體驗,反而更差了。

不可以頻繁使用PostMessage發送同一個消息,除非保證上一次發送的消息被處理完成(這如何保證???),這還不如直接用SendMessage。

 

當然OnMyTest函數可能是這樣的:

LRESULT CMyDlg::OnMyTest(WPARAM wParam, LPARAM lParam)

{

     static int nTimes = 0;

     CString strOutPut;

 

     int* pParam1 = (int*)wParam;

     int* pParam2 = (int*)lParam;

     nTimes++;

 

     strOutPut.Format(_T("%s%d   %s%d %s%d"),

         _T("Param1 = "), *pParam1,

         _T("Param2 = "), *pParam2,

         _T("RealTimes = "), nTimes);

 

     OutputDebugString(strOutPut);

 

     // 大量訪問網絡,磁盤等低速操作

     return 0;

}

在這種情況下,如果用SendMessage的話,用戶體驗將會大大下降,甚至導致程序無法響應。於是有人提出了使用PostMessage,這樣程序不會無法響應,最多顯示不正確罷了。乍一看,提議似乎還不錯,至少程序正常運行了。但是,這些網絡訪問、磁盤讀寫等操作為什么要放到界面的代碼中呢?界面、代碼分離才是合理的,因此可以認定,訪問網絡、磁盤讀寫等操作不應該放到這里來處理。

 

2)  在非主線程中Post消息,以處理進度條顯示(用WM_MY_TEST的參數WPARAM、LPARAM來處理進度條的顯示)

代碼:code_2

DWORD WINAPI ThreadProc( LPVOID lpParam )

{

     CMyDlg *pThis = (CMyDlg *)lpParam;

     int nParam1 = 0;

     int nParam2 = 0;

 

     for (int nIndex = 0; nIndex < 1000; nIndex++)

     {

         nParam1++;

         nParam2++;

         pThis->PostMessage(WM_MY_TEST, (WPARAM)&(nParam1), (LPARAM)&(nParam2));

     }

 

     return 0;

}

 

void CMyDlg::OnBnClickedOk()

{

     HANDLE hThread = CreateThread(NULL,

         0,

         ThreadProc, 

         (void*)this,

         0,

         NULL);

     //OnOK();

}

 

LRESULT CMyDlg::OnMyTest(WPARAM wParam, LPARAM lParam)

{

     static int nTimes = 0;

     CString strOutPut;

 

     int* pParam1 = (int*)wParam;

     int* pParam2 = (int*)lParam;

     nTimes++;

 

     strOutPut.Format(_T("%s%d   %s%d %s%d"),

         _T("Param1 = "), *pParam1,

         _T("Param2 = "), *pParam2,

         _T("RealTimes = "), nTimes);

 

     OutputDebugString(strOutPut);

     return 0;

}

 

code_2的運行結果:

(程序直接崩潰了)

線程函數不等待WM_MY_TEST的返回,循環1000次之后直接退出了,這導致棧上的變量nParam1、nParam2被釋放,然后OnMyTest處理的時候,nParam1、nParam2的地址已經無效了,導致崩潰。SendMessage則不會出現此類情況。

 

修改程序

代碼:code_2(2)

DWORD WINAPI ThreadProc( LPVOID lpParam )

{

     CqwerDlg *pThis = (CqwerDlg *)lpParam;

     int *nParam1 = NULL;

     int *nParam2 = NULL;

 

     nParam1 = new int;

     nParam2 = new int;

 

     for (int nIndex = 0; nIndex < 1000; nIndex++)

     {

         *nParam1 = nIndex;

         *nParam2 = nIndex;

         pThis->PostMessage(WM_MY_TEST, (WPARAM)nParam1, (LPARAM)nParam2);

     }

 

     return 0;

}

由於堆內存沒有被釋放,所以程序沒有崩潰,在我的機器上運行結果為:

Param1 = 27 Param2 = 27 RealTimes = 1

Param1 = 117 Param2 = 117 RealTimes = 2

Param1 = 162 Param2 = 162 RealTimes = 3

Param1 = 218 Param2 = 218 RealTimes = 4

Param1 = 272 Param2 = 272 RealTimes = 5

Param1 = 312 Param2 = 312 RealTimes = 6

Param1 = 353 Param2 = 353 RealTimes = 7

Param1 = 391 Param2 = 391 RealTimes = 8

Param1 = 431 Param2 = 431 RealTimes = 9

程序執行非常不穩定,每次結構都不同,當然也不能用這些數據了。當把兩個new int放到for循環中,執行結果是穩定的,但這樣的代碼晦澀難懂。在這里用PostMessage沒有任何好處,所以建議使用SendMessage。

 

       分析想法2:

       1)  已知一個線程處理了A,由於其他需要,此線程還需要處理B(必須在A完成之后)。需要新加入代碼來實現,以前的代碼為:

代碼:code_3

DWORD WINAPI ThreadProc(LPVOID lpParam)

{

     HWND hWnd = (HWND)lpParam;

     // Do some things

     ::PostMessage(hWnd, WM_MUST_DO_THING_A, 0, 0);

     return 0;

}

LRESULT CMyDlg::OnMustDoThingA(WPARAM wParam, LPARAM lParam)

{

     Do some things for A

 

     Do some things for B   // 費解,這是A的處理函數!!!

}

我們可以多加個消息,WM_MUST_DO_THING_B,然后用PostMessage發送,哦,不能這樣,B一定要在A完成之后,現在唯一的處理方式只有對B的處理加入到A的消息處理函數中,這將導致費解的代碼。如果在原來的線程函數中PostMessage為SendMessage,則不會如此。

    如果A、B是不相關聯的兩個操作,為了以后擴展,也不該用PostMessage,這種情況下應該多創建一個線程進行處理。

 

2)  對於需要處理的比較重要的操作(這些可能導致卡死):

LRESULT CMyDlg::OnDoThing(WPARAM wParam, LPARAM lParam)

{

    Things To do. // 這里可能會卡死,但又必須處理

}

在這種情況下,建議使用SendMessageTimeout,當等待一段時間后,消息仍然沒有處理完成,則程序放棄操作繼續運行。

 

    3)對於所有無關緊要的操作:

    這些操作包括:清理磁盤臨時文件等等,這些操作有沒正常處理,程序並不關心,在這種情況下,則可使用PostMessage、

 

終上所述,我們得到如下結論:

1、  PostMessage不能頻繁的發送同一個消息,除非保證上次Post過的消息處理完成。

2、  如果用SendMessage導致應用程序用戶體驗下降,應該檢查消息處理函數,而不僅僅簡單改為PostMessage。

3、  如果消息是程序必須處理的,則不能使用PostMessage。

4、  如果消息是程序必須處理,而又有可能導致程序卡死,則使用SendMessageTimeout。

5、  如果消息是無關緊要的,則可以建議使用PostMessage。

6、  對於WM_HOTKEY 等Windows特定的消息,則只能使用PostMessage(未在本文中說明)。

參考:http://blog.csdn.net/xt_xiaotian/article/details/2778689


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM