天天看點

多線程式列槽通信 MFC CSerialPort

原文轉載自http://www.cnblogs.com/xingrun/p/3587144.html?utm_source=tuicool&utm_medium=referral。

以下全為轉載:

寫在前面:

         晚上應該繼續完成未寫完的代碼,但Chrome上打開的标簽實在太多了,約30個了,必須關掉一些,是以需要把自己看的整理一下然後關掉。本次主要寫點MFC環境下多線程式列槽通信相關的東西,這包括線程建立及控制、序列槽同步異步操作、記憶體非法通路(或者說是線程同步)、線程通信、Windows消息響應過程等。

遇到問題:

         項目中IO傳感器通信子產品之前直接寫在了主線程中,UI代碼和序列槽通信代碼攪合在一起,不利于後期維護,而且有個非常嚴重的問題,IO通信太忙導緻整個系統比較卡,特别是當系統接上超過3個錄影機之後,MFC模态對話框使用Domodal()直接無法打開,卡住了,然後使用者就無法操作了,這個問題必須要解決。

解決方案:

         單獨開辟一個線程來處理所有的序列槽通信,該IO線程和主線程(負責UI部分)通信,進而更新狀态,不能子線程中直接更新UI,參看《MFC最好不要在子線程中操控界面上的控件》。

具體步驟:

1.創立IO線程并完成消息響應

1 2

HANDLE

hThread1 = CreateThread( NULL,0,IOControlProc,(

LPVOID

)(m_pCOMSerialPort),0,&m_dwIOControlThreadId );

//建立IO線程

CloseHandle( hThread1 );

//關閉線程句柄

  其中

1 2

CSerialPort  *m_pCOMSerialPort;

//通信序列槽

DWORD

m_dwIOControlThreadId;

//線程ID

  IOControlProc線程函數

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49

DWORD

WINAPI IOControlProc(

LPVOID

lpParameter)

{

CSerialPort *pSerialPort = (CSerialPort*)lpParameter;

UINT_PTR

nHUMITimer = 0;

UINT_PTR

nIOTimer = 0;

//::SetTimer(NULL,NULL,200,(TIMERPROC)TimerProc);

nHUMITimer = ::SetTimer(NULL,TEMPHUMICOMM_TIMER,500,(TIMERPROC)OnIOTimer);

//溫濕度

nIOTimer =::SetTimer(NULL,IOCOMM_TIMER,100,(TIMERPROC)OnIOTimer);

//IO

PIOTimerStru pHumiTimer= 

new

IOTimerStru;

pHumiTimer->nIdEvent = TEMPHUMICOMM_TIMER;

pHumiTimer->pSerialPort = pSerialPort;

PIOTimerStru pIoTimer = 

new

IOTimerStru;

pIoTimer->nIdEvent = IOCOMM_TIMER;

pIoTimer->pSerialPort = pSerialPort;

PIOTimerStru pIoControl = 

new

IOTimerStru;

pIoControl->nIdEvent = TEMPHUMICOMM_TIMER + IOCOMM_TIMER;

pIoControl->pSerialPort = pSerialPort;

LPARAM

lParam;

MSG msg;

while

(GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg);

if

(WM_TIMER == msg.message)

{

lParam = msg.lParam;

//WM_TIMER回調函數的位址

if

(nHUMITimer == msg.wParam)

{

msg.wParam = (

WPARAM

)pHumiTimer;

}

else

if

(nIOTimer == msg.wParam)

{

msg.wParam = (

WPARAM

)pIoTimer;

}          

}  

else

if

(WM_IOCOMMDATA == msg.message)

{

msg.message = WM_TIMER;

pIoControl->wParam = msg.wParam;

pIoControl->lParam = msg.lParam;

msg.wParam = (

WPARAM

)pIoControl;

msg.lParam = lParam;

}      

DispatchMessage(&msg);

}

delete

pHumiTimer;

delete

pIoTimer;

delete

pIoControl;

return

0;

}

  這裡,IO線程有自己的消息循環隊列(雖然沒有視窗),參看:《子線程裡如何使用定時器》,把這裡的代碼改成死循環的(參看《是否在子線程内使用SetTimer?》),如下

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

VOID

CALLBACK TimerProc(          

HWND

hwnd,

UINT

uMsg,

UINT_PTR

idEvent,

DWORD

dwTime

){

//do some thing

return

;

}

DWORD

WINAPI ThreadProc(

LPVOID

lpParameter)

{

::SetTimer(NULL,NULL,200,(TIMERPROC)TimerProc);

MSG msg;

while

(GetMessage(&msg,NULL,0,0))

{

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return

0;

}

  我的消息響應函數寫成如下

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31

VOID

CALLBACK OnIOTimer(

HWND

hwnd,

UINT

uMsg,

UINT_PTR

idEvent,

DWORD

dwTime)

{

PIOTimerStru pStru = (PIOTimerStru)idEvent;

if

(pStru->nIdEvent == TEMPHUMICOMM_TIMER)

//溫濕度子產品

{

static

bool

bFlag = 

true

;

if

(bFlag)

{

IOSendData(pStru->pSerialPort,2,0x0A,0,0x03);

}

else

{

IOSendData(pStru->pSerialPort,2,0x0A,0,0x04);

}

bFlag = !bFlag;

}

else

if

(pStru->nIdEvent == IOCOMM_TIMER)

//IO控制子產品

{

IOSendData(pStru->pSerialPort,1,0x10,0,0x03);

}

else

if

(pStru->nIdEvent ==  TEMPHUMICOMM_TIMER + IOCOMM_TIMER)

//别的線程發送的指令

{      

WORD

highDeviceIndex = HIWORD(pStru->wParam);

//裝置号

WORD

lowPortIndex = LOWORD(pStru->wParam);

//裝置1端口号,裝置2指定溫度或濕度子產品

WORD

highParam = HIWORD(pStru->lParam);

//裝置1指定狀态1或者0,裝置2指定整數部分

WORD

lowParam = LOWORD(pStru->lParam);

//裝置1為0,裝置2指定小數部分

IOSendData(pStru->pSerialPort,(

int

)highDeviceIndex,(

BYTE

)highParam,(

BYTE

)lowParam,(

BYTE

)lowPortIndex);

}

return

;

}

  這裡要注意一個問題,OnIOTimer中獲得的idEvent并不是我們設定的IOCOMM_TIMER或者TEMPHUMICOMM_TIMER,究其原因,參看《SetTimer在無視窗和有視窗線程的使用》,文中關于原因的解釋為“注:隻有當hWnd參數為非空時,計時器的ID為設定的 nIDEvent, 系統為你自動生成一個計時器ID,可由傳回時值擷取.”,可由MSDN得知。

那麼我們的這個IOCOMM_TIMER或者TEMPHUMICOMM_TIMER,怎麼傳遞過去呢?檢視《怎麼往SetTimer的回調函數傳遞參數》得知,我們可由msg.wParam傳遞。

那麼對于我們自定義的消息TEMPHUMICOMM_TIMER + IOCOMM_TIMER,我也想讓OnIOTimer來處理怎麼辦?直接修改msg.message = WM_TIMER;就可以了嘛?答案是不行的!

為什麼呢?參看《消息循環中的TranslateMessage函數和DispatchMessage函數》,原來“如果參數lpmsg指向一個WM_TIMER消息,并且WM_TIMER消息的參數IParam不為NULL,則調用IParam指向的函數,而不是調用視窗程式。”,那麼我們直接修改msg.lParam為OnIOTimer函數的位址就行了。這樣,所有消息響應完成了。

2.多線程通路沖突(線程沖突)

遇到一個問題,系統有一個序列槽通信端口,IO線程直接使用了,然後我在主線程中也發送資料,然後問題出現了,有時候會出現非法通路,跟蹤了一下,原來兩個線程使用了同一個序列槽通信緩沖區,主線程往裡面壓入資料的時候,可能子線程已經釋放了該緩沖區,查詢文章《CSerialPort連續發送大量資料時出錯原因分析》,而我使用的CSerialPort是同步的,我嘗試修改類庫代碼,報錯太多,此方法放棄。最終,我的解決方法是把所有的序列槽IO通信全部交給子線程來做,那麼主線程要做的事,可以通過發送消息給子線程,再由子線程代勞,主線程怎麼發送消息給子線程?使用API函數PostThreadMessage來完成,第一個參數就是子線程的Id。

子線程收到資料後,進行驗證,驗證通過後,發消息給主線程,通知它更新界面。《主線程與子線程間通信解決辦法 - VC/MFC》

3.主線程退出前,關閉子線程

1 2

PostThreadMessage(m_dwIOControlThreadId,WM_QUIT,0,0);

Sleep(100);

//需要等待關閉掉

GetMessage有消息時且消息不為WM_QUIT時傳回TRUE,如果有消息且為WM_QUIT則傳回FALSE,沒有消息時不傳回。  

這裡可參看《如何正确的關閉 MFC 線程》和《GetMessage和PeekMessage的聯系與差別以及用法 TranslateMessage與DispatchMessage 》

至此,經測試問題全部解決,記錄一下。