P2PMessageQueue的實際用法
使用 P2PMessageQueue
本部分,您將看到使用 P2PMessageQueue 類和相關類型的示例。
注 當運行該示例時,可以選擇部署到 Windows CE 或 Pocket PC 2003 模擬器或設備。您可以在不進行修改的情況下在任一個平臺上調試該項目(并運行該應用程序)。運行示例時,看到的第一個屏幕提示您在閱讀器進程和發送器進程間進行選擇。無論選擇哪一個,都必須再次運行 .exe 文件(從 Program Files),然后選擇另一個選項。在 Pocket PC 平臺上,無論是使用模擬器還是設備,都必須重命名 .exe 文件(否則將激活現有的運行中應用程序)。
字符串的簡單 IPC 交換
首先使用托管進程將字符串傳入另一個 .NET Compact Framework 應用程序(也可以使發送方或接收方成為本機應用程序。有三種不同的方法用來讀取接收端的字符串(相同的原理也適用于發送端):阻塞、非阻塞以及事件驅動。
發送方和接收方的圖形用戶界面 (GUI) 在功能方面是自描述性的,如圖 3、4 和 5 所示。
圖 3. 主窗體
圖 4. 讀取端
當發送方單擊 Send 按鈕時,發送一個字符串(如文本框中輸入的),并可以選擇將該消息設置為警告消息(基于 Message Is Alert 復選框狀態)。發送方會阻塞,直到針對指定超時發送該消息(作為 combobox 中選擇的發送方)。位于該復選框下面的窗體底部顯示 Send 方法的返回結果,如圖 5 所示(即 OK)。
圖 5. 發送 / 編寫端
這里再次使用了下載示例中的 Send 方法。
- private void cmdSend_Click(object sender, System.EventArgs e) {
- Message msg;
- msg = new Message(
- System.Text.Encoding.ASCII.GetBytes(txtSend.Text),
- chkIsAlert.Checked);
- ReadWriteResult rwr = mQue.Send(msg, mTimeout);
- lblSendResult.Text = rwr.ToString();
- }
當閱讀器收到一個消息后,會將它顯示在列表視圖(第三列)中,并指出它是否是警告消息(第二列)。當成功接收到該消息時,第一列將始終顯示 OK。默認情況下,要接收一個消息,請單擊 Receive 按鈕;如果沒有消息要接收或者方法失敗,則列表視圖的第一列將指出原因(另兩列在該情形中不適用)。
在讀取和發送時,Queue Info 菜單(單擊 Info,然后單擊 Queue Info)會顯示有關隊列的數據。接收端上的 Mode 菜單(單擊 Read,然后單擊 Mode)有三個菜單項:On Demand Only、Event driven 和 Block a Thread。這些項用于配置該程序如何接收隊列外的消息。當您選擇一個模式后,它在示例應用程序的生命周期內不應該更改(開發人員可針對自己的設計進行混合與匹配)。以下幾個小節描述三種讀取模式。
按命令讀(對應于菜單 On Demand Only )
當接收方單擊 Receive 按鈕時,將執行以下方法。
- private void cmdReceive_Click(object sender, System.EventArgs e) {
- Message msg;
- msg = new Message();
- // mTimeout is set by the end user by means of the GUI
- // to DON'T BLOCK (0), BLOCK (-1), or a real timeout value
- ReadWriteResult rwr = mQue.Receive(ref msg, mTimeout);
- ListViewItem lvi;
- if (rwr == ReadWriteResult.OK){
- bool isAlrt;
- string payload;
- isAlrt = msg.IsAlert;
- byte[] bytes = msg.MessageBytes;
- payload = System.Text.Encoding.ASCII.GetString(
- bytes, 0, bytes.GetLength(0));
- lvi = new ListViewItem(
- new string[]{rwr.ToString(), isAlrt.ToString(), payload});
- }else{
- lvi = new ListViewItem(
- new string[]{rwr.ToString(), @"n\a", @"n\a"});
- }
- listView1.Items.Add(lvi);
- listView1.Columns[2].Width = -2;}
事件驅動
事件驅動模型基本上意味著應用程序不會在任意時刻通過調用 Receive(例如,在計時器上或者要求用戶單擊 Receive 按鈕)來輪詢新消息,相反,應用程序會訂閱并捕獲來自 P2PMessageQueue 類的事件。要訂閱事件,需要使用正規的 .NET Compact Framework 委托習語(隊列的創建也不例外)。
- mQue = new P2PMessageQueue(
- isReader, txtQueueName.Text, maxL, maxM, out firstTime);
- mQue.DataOnQueueChanged += new EventHandler(mQue_DataOnQueueChanged);
引發該事件會調用方法,在本例中,只調用現有的接收方法。
- private void mQue_DataOnQueueChanged(object sender, EventArgs e) {
- this.Invoke(new EventHandler(this.cmdReceive_Click));
- }
阻塞線程
第三種從隊列進行讀取的方法是:創建一個線程,并使其阻塞以等待隊列的 Receive 方法。每次接收到消息時,應用程序都會處理它,然后再次循環回阻塞。以下是一些帶有解釋的示例代碼。
您會在某個地方創建并啟動以下線程。
- Thread t = new Thread(new ThreadStart(ThreadBlockedOnRead));
- t.Start();
該線程用下面的方法運行。(有關更多上下文,請下載代碼)。
- private void ThreadBlockedOnRead(){
- while (mMode == 2){ // Thread mode
- Message msg = new Message();
- //Can actually omit a timeout for a true infinite block
- ReadWriteResult rwr = mQue.Receive(msg, 60 * 1000);
- if (rwr == ReadWriteResult.InvalidHandle || mMode != 2){
- return;
- }
- string body = rwr.ToString();
- if (rwr == ReadWriteResult.OK){
- byte[] bytes = msg.MessageBytes;
- string bytesAsString =
- System.Text.Encoding.ASCII.GetString(
- bytes, 0, bytes.GetLength(0));
- body += " | " + msg.IsAlert.ToString() + " | " + bytesAsString;
- }
- MessageBox.Show(body, "To terminate this mode use the menu again");
- }
- }
基本示例代碼至此結束。讀取隊列的三種方式也可以應用于通過隊列發送。當事件接收到信號時(即,隊列從已滿轉變為未滿),可進行阻塞和發送或嘗試在不進行阻塞的情況下隨時進行發送。在下一部分中,您將看到該設計如何允許隊列中的消息包含其他結構 — 而不僅僅是字符串。
注 在開發一個依賴事件信號來識別隊列從已滿轉變為未滿的應用程序時,需要在應用程序啟動時針對隊列執行一個初始寫入操作。如果不執行該初始寫入操作,則應用程序永遠不會開始寫入,因為該初始寫入操作必須經過特定地執行才能填充轉變為未滿狀態的隊列,因此,向事件發出信號以觸發針對該隊列的進一步寫入操作。
發送和接收更復雜的類型(不僅僅是字符串)
在前幾部分中,是在應用程序之間傳遞字符串,但如果可以將字符串與字節數組進行轉換,則還可以傳遞任何數據類型。因此,將該類型轉換為一個字節數組,在其中創建 Message 類,然后發送 Message。在接收端,檢索 Message,獲取字節數組,然后在其中創建該類型(例如,公開類型的 ToBytes 和 FromBytes 方法)。另一個方法是,從 Message 類繼承自己的類,并在其中實現轉換。很自然,如果您嘗試傳遞一個復雜的對象圖,則在類型與字節數組之間進行轉換會難得多。嘗試使用沒有源代碼的類型需要特別注意,因為您可能不具有對該類型的完整狀態(例如,私有成員)的訪問權,因此,可能無法準確地在類型與字節數組之間進行轉換。
出于簡單的目的,假設將一個 Int64 和一個 Boolean 從一個進程傳遞到另一個進程。將創建一個 CustomMessage 類,如下所示。
- public class CustomMessage : Message {
- public long TotalMemory;
- public bool AfterGC;
- public CustomMessage(){
- TotalMemory = 0;
- AfterGC = false;
- }
- public CustomMessage(long totMem, bool afterGarbCol){
- TotalMemory = totMem;
- AfterGC = afterGarbCol;
- }
- public override byte[] MessageBytes {
- get {
- byte[] b1 = BitConverter.GetBytes(TotalMemory);
- byte[] b2 = BitConverter.GetBytes(AfterGC);
- byte[] b = new byte[9];
- Buffer.BlockCopy(b1, 0, b, 0, 8);
- Buffer.BlockCopy(b2, 0, b, 8, 1);
- return b;
- }
- set {
- TotalMemory = BitConverter.ToInt64(value, 0);
- AfterGC = BitConverter.ToBoolean(value, 8);
- base.MessageBytes = value;
- }
- }
- }
添加了兩個感興趣的字段(TotalMemory、AfterGC),重寫 MessageBytes 屬性以實現轉換(get 和 set 方法位于轉換發生的位置),然后添加一個默認的構造函數(這個參數化的構造函數是可選的)。
現在,如果要使用前面的示例,只需更改兩處地方:
發送消息時,要創建一個 CustomMessage,而不是將值賦給 TotalMemory 和 AfterGC。
- //msg = new Message(. . .); //Instead of this line
- msg = new CustomMessage(GC.GetTotalMemory(false), false);
- ReadWriteResult rwr = mQue.Send(msg, mTimeout);
接收消息時,要創建一個 CustomMessage。
- //msg = new Message();
- msg = new CustomMessage(); //msg still declared as Message
- ReadWriteResult rwr = mQue.Receive(msg, mTimeout);
然后,在 mQue.Receive 返回時讀取它的屬性。
- //byte[] bytes = msg.MessageBytes;
- //payload = System.Text.Encoding.ASCII.GetString(. . .);
- payload = "Total Memory = " + ((CustomMessage)msg).TotalMemory.ToString() +
- (((CustomMessage)msg).AfterGC ? " after a GC" : " without forcing a GC");
【編輯推薦】