[.NET] 實作訊息迴圈 (Message Loops): Part 1

想必大家應該都有聽過 Windows 內有一個訊息迴圈 (Message Loop) 吧,這個 Loop 是每個 Windows 應用程式都有的東西,藉由 GetMessage(), DispatchMessage() 與 TranslateMessage() 三個 API,將訊息分派給各自的處理常式去處理。

這是我最近在寫的一個案子所練到的東西,想當然耳,這個案子一定是持續有資料來回才會需要做迴圈,但這個案子的對象不是什麼 RS-232, RS-485, USB或是什麼硬體,而是 MSN Server。是的,我這個案子正是要做 MSN 機器人,不過我不太想要用一些現有的元件,而且剛好又有很多大德開放了他們的 MSN Protocol 研究的資訊,可以一窺 MSN Protocol 到底是在做什麼的,也可以順著做出一個簡單的 MSN 機器人,甚至 MSN Client 都可以。

好,回到主題。

想必大家應該都有聽過 Windows 內有一個訊息迴圈 (Message Loop) 吧,這個 Loop 是每個 Windows 應用程式都有的東西,藉由 GetMessage(), DispatchMessage() 與 TranslateMessage() 三個 API,將訊息分派給各自的處理常式去處理,而這三個組成的訊息迴圈會像:

// language: C
while( (bRet = GetMessage( &msg, hWnd, 0, 0 )) != 0)
{ 
    if (bRet == -1)
    {
        // handle the error and possibly exit
    }
    else
    {
        TranslateMessage(&msg); 
        DispatchMessage(&msg); 
    }
}

對 1

(圖來源:http://msdn.microsoft.com/zh-tw/library/dd229215.aspx)

簡單,但威力強大。

如果我們想要在自己的程式中實作這樣一個訊息迴圈,那我們就需要模擬一個 while 的迴圈,並且在迴圈中抓取來自內部或外部的訊息 (ex: Server 回傳的指令),而在迴圈中抓到指令時,就處理指令或是略過它,因此有了這樣的原型:

// language: C#
while (true)
{
    bool messageFetched = false;

    // check message.
    // _ns is a network stream created by TcpClient.GetStream() method.
    if (this._ns.DataAvailable)
    {
        // read message.
        ...
        // set fetched flag.
        messageFetched = true;
    }

    if (messageFetched)
    {
        // dispatch message (can implemented by Command Pattern)
    }

    Thread.Sleep(50); // or 100, 150, unit: ms

}

 

在原型程式碼中,檢查 NetworkStream.DataAvailable 是否為 true 的那一段,其實就是模擬 GetMessage() 的動作,當然真正在執行時,GetMessage() 是會進入 Message Queue 中抓取最後一個 WM 訊息,而在這裡的原型程式中,它是檢查 server 有沒有回傳指令。如果有讀到指令時,messageFetched 旗標會設為 true,然後接下來的程式就是處理 TranslateMessage() 與 DispatchMessage() 的工作,分析與分派訊息,而訊息的處理可以進一步用 Command Pattern + Command Factory 來做,以簡化用戶端程式。最後要讓 Thread 可以休息一下,以讓 CPU 可以切出去處理其他的一些事,如果沒有讓它休息的話,CPU 的佔用率會升高。

訊息迴圈由於是會持續的執行,所以不能將它放在主執行緒中,必須要另外啟動一個執行緒來執行它,所以就會有這樣的程式碼:

// language: C#
Thread messageLoopThread = new Thread(new ThreadStart(this.MessageLoopProcedure));
messageLoopThread.IsBackground = true;
messageLoopThread.Start();

 

不過這樣會有個問題,就是當主執行緒離開時,這個執行緒會離不開,會需要有一個通知機制讓執行緒知道要離開,或是用更強硬的手法:Thread.Abort(); 來處理,若訊息迴圈是簡單的分析與派送訊息時就還好,但如果迴圈中會用到一些資源的話,可能會有放不掉的問題或清理不乾淨的現象。

PS1: 我是 threading 的苦手,所以如果觀念有誤請指正。
PS2: 未來會不會有 MSN 的 Codeplex 專案,我不知道,時候到了自然會有 :)

Reference:
http://msdn.microsoft.com/en-us/library/ms644936(v=vs.85).aspx