本文將使用C++語言,在MFC框架的配合下給出PostMessage、SendMessage等的使用方式與使用不當形成的後果(討論均針對自定義的消息進行)。若有什麼錯誤,歡迎指正。網絡
寫過Windows程序的同窗都知道PostMessage、SendMessage的區別,PostMessage函數調用發送以後,當即返回,不等待消息處理完成。而SendMessage則讓調用的線程處於阻塞(BLOCk)狀態,直到消息處理完成。框架
正因爲這兩個函數的區別致使了以下想法:函數
想法1:PostMessage當即返回,在程序中,處理界面顯示(如處理進度條、滾動條等)時使用PostMessage,不會影響程序的用戶體驗。學習
想法2:在程序中全用PostMessage,放棄SendMessage,好處:PostMessage是當即返回的,能夠不影響程序的正常流程,就算在消息處理函數中卡死了,也不影響主線程的運行。this
起初「學習」到了這些想法,覺得受益不淺,但通過一段時間以後,發現此兩種想法都是不可取的。.net
分析想法1:線程
這裏可分爲兩點:code
1) 在主線程中Post消息,以處理進度條顯示(用WM_MY_TEST的參數WPARAM、LPARAM來處理進度條的顯示)orm
代碼:code_1blog
#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的消息參數全被沖掉了。用pParam一、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次以後直接退出了,這致使棧上的變量nParam一、nParam2被釋放,而後OnMyTest處理的時候,nParam一、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、
終上所述,咱們獲得以下結論:
一、 PostMessage不能頻繁的發送同一個消息,除非保證上次Post過的消息處理完成。
二、 若是用SendMessage致使應用程序用戶體驗降低,應該檢查消息處理函數,而不只僅簡單改成PostMessage。
三、 若是消息是程序必須處理的,則不能使用PostMessage。
四、 若是消息是程序必須處理,而又有可能致使程序卡死,則使用SendMessageTimeout。
五、 若是消息是可有可無的,則能夠建議使用PostMessage。
六、 對於WM_HOTKEY 等Windows特定的消息,則只能使用PostMessage(未在本文中說明)。
參考:http://blog.csdn.net/xt_xiaotian/article/details/2778689