P2PMessageQueue的實(shí)際用法
使用 P2PMessageQueue
本部分,您將看到使用 P2PMessageQueue 類和相關(guān)類型的示例。
注 當(dāng)運(yùn)行該示例時(shí),可以選擇部署到 Windows CE 或 Pocket PC 2003 模擬器或設(shè)備。您可以在不進(jìn)行修改的情況下在任一個(gè)平臺(tái)上調(diào)試該項(xiàng)目(并運(yùn)行該應(yīng)用程序)。運(yùn)行示例時(shí),看到的第一個(gè)屏幕提示您在閱讀器進(jìn)程和發(fā)送器進(jìn)程間進(jìn)行選擇。無論選擇哪一個(gè),都必須再次運(yùn)行 .exe 文件(從 Program Files),然后選擇另一個(gè)選項(xiàng)。在 Pocket PC 平臺(tái)上,無論是使用模擬器還是設(shè)備,都必須重命名 .exe 文件(否則將激活現(xiàn)有的運(yùn)行中應(yīng)用程序)。
字符串的簡單 IPC 交換
首先使用托管進(jìn)程將字符串傳入另一個(gè) .NET Compact Framework 應(yīng)用程序(也可以使發(fā)送方或接收方成為本機(jī)應(yīng)用程序。有三種不同的方法用來讀取接收端的字符串(相同的原理也適用于發(fā)送端):阻塞、非阻塞以及事件驅(qū)動(dòng)。
發(fā)送方和接收方的圖形用戶界面 (GUI) 在功能方面是自描述性的,如圖 3、4 和 5 所示。
圖 3. 主窗體
圖 4. 讀取端
當(dāng)發(fā)送方單擊 Send 按鈕時(shí),發(fā)送一個(gè)字符串(如文本框中輸入的),并可以選擇將該消息設(shè)置為警告消息(基于 Message Is Alert 復(fù)選框狀態(tài))。發(fā)送方會(huì)阻塞,直到針對(duì)指定超時(shí)發(fā)送該消息(作為 combobox 中選擇的發(fā)送方)。位于該復(fù)選框下面的窗體底部顯示 Send 方法的返回結(jié)果,如圖 5 所示(即 OK)。
圖 5. 發(fā)送 / 編寫端
這里再次使用了下載示例中的 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();
- }
當(dāng)閱讀器收到一個(gè)消息后,會(huì)將它顯示在列表視圖(第三列)中,并指出它是否是警告消息(第二列)。當(dāng)成功接收到該消息時(shí),第一列將始終顯示 OK。默認(rèn)情況下,要接收一個(gè)消息,請單擊 Receive 按鈕;如果沒有消息要接收或者方法失敗,則列表視圖的第一列將指出原因(另兩列在該情形中不適用)。
在讀取和發(fā)送時(shí),Queue Info 菜單(單擊 Info,然后單擊 Queue Info)會(huì)顯示有關(guān)隊(duì)列的數(shù)據(jù)。接收端上的 Mode 菜單(單擊 Read,然后單擊 Mode)有三個(gè)菜單項(xiàng):On Demand Only、Event driven 和 Block a Thread。這些項(xiàng)用于配置該程序如何接收隊(duì)列外的消息。當(dāng)您選擇一個(gè)模式后,它在示例應(yīng)用程序的生命周期內(nèi)不應(yīng)該更改(開發(fā)人員可針對(duì)自己的設(shè)計(jì)進(jìn)行混合與匹配)。以下幾個(gè)小節(jié)描述三種讀取模式。
按命令讀(對(duì)應(yīng)于菜單 On Demand Only )
當(dāng)接收方單擊 Receive 按鈕時(shí),將執(zhí)行以下方法。
- 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;}
事件驅(qū)動(dòng)
事件驅(qū)動(dòng)模型基本上意味著應(yīng)用程序不會(huì)在任意時(shí)刻通過調(diào)用 Receive(例如,在計(jì)時(shí)器上或者要求用戶單擊 Receive 按鈕)來輪詢新消息,相反,應(yīng)用程序會(huì)訂閱并捕獲來自 P2PMessageQueue 類的事件。要訂閱事件,需要使用正規(guī)的 .NET Compact Framework 委托習(xí)語(隊(duì)列的創(chuàng)建也不例外)。
- mQue = new P2PMessageQueue(
- isReader, txtQueueName.Text, maxL, maxM, out firstTime);
- mQue.DataOnQueueChanged += new EventHandler(mQue_DataOnQueueChanged);
引發(fā)該事件會(huì)調(diào)用方法,在本例中,只調(diào)用現(xiàn)有的接收方法。
- private void mQue_DataOnQueueChanged(object sender, EventArgs e) {
- this.Invoke(new EventHandler(this.cmdReceive_Click));
- }
阻塞線程
第三種從隊(duì)列進(jìn)行讀取的方法是:創(chuàng)建一個(gè)線程,并使其阻塞以等待隊(duì)列的 Receive 方法。每次接收到消息時(shí),應(yīng)用程序都會(huì)處理它,然后再次循環(huán)回阻塞。以下是一些帶有解釋的示例代碼。
您會(huì)在某個(gè)地方創(chuàng)建并啟動(dòng)以下線程。
- Thread t = new Thread(new ThreadStart(ThreadBlockedOnRead));
- t.Start();
該線程用下面的方法運(yùn)行。(有關(guān)更多上下文,請下載代碼)。
- 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");
- }
- }
基本示例代碼至此結(jié)束。讀取隊(duì)列的三種方式也可以應(yīng)用于通過隊(duì)列發(fā)送。當(dāng)事件接收到信號(hào)時(shí)(即,隊(duì)列從已滿轉(zhuǎn)變?yōu)槲礉M),可進(jìn)行阻塞和發(fā)送或嘗試在不進(jìn)行阻塞的情況下隨時(shí)進(jìn)行發(fā)送。在下一部分中,您將看到該設(shè)計(jì)如何允許隊(duì)列中的消息包含其他結(jié)構(gòu) — 而不僅僅是字符串。
注 在開發(fā)一個(gè)依賴事件信號(hào)來識(shí)別隊(duì)列從已滿轉(zhuǎn)變?yōu)槲礉M的應(yīng)用程序時(shí),需要在應(yīng)用程序啟動(dòng)時(shí)針對(duì)隊(duì)列執(zhí)行一個(gè)初始寫入操作。如果不執(zhí)行該初始寫入操作,則應(yīng)用程序永遠(yuǎn)不會(huì)開始寫入,因?yàn)樵摮跏紝懭氩僮鞅仨毥?jīng)過特定地執(zhí)行才能填充轉(zhuǎn)變?yōu)槲礉M狀態(tài)的隊(duì)列,因此,向事件發(fā)出信號(hào)以觸發(fā)針對(duì)該隊(duì)列的進(jìn)一步寫入操作。
發(fā)送和接收更復(fù)雜的類型(不僅僅是字符串)
在前幾部分中,是在應(yīng)用程序之間傳遞字符串,但如果可以將字符串與字節(jié)數(shù)組進(jìn)行轉(zhuǎn)換,則還可以傳遞任何數(shù)據(jù)類型。因此,將該類型轉(zhuǎn)換為一個(gè)字節(jié)數(shù)組,在其中創(chuàng)建 Message 類,然后發(fā)送 Message。在接收端,檢索 Message,獲取字節(jié)數(shù)組,然后在其中創(chuàng)建該類型(例如,公開類型的 ToBytes 和 FromBytes 方法)。另一個(gè)方法是,從 Message 類繼承自己的類,并在其中實(shí)現(xiàn)轉(zhuǎn)換。很自然,如果您嘗試傳遞一個(gè)復(fù)雜的對(duì)象圖,則在類型與字節(jié)數(shù)組之間進(jìn)行轉(zhuǎn)換會(huì)難得多。嘗試使用沒有源代碼的類型需要特別注意,因?yàn)槟赡懿痪哂袑?duì)該類型的完整狀態(tài)(例如,私有成員)的訪問權(quán),因此,可能無法準(zhǔn)確地在類型與字節(jié)數(shù)組之間進(jìn)行轉(zhuǎn)換。
出于簡單的目的,假設(shè)將一個(gè) Int64 和一個(gè) Boolean 從一個(gè)進(jìn)程傳遞到另一個(gè)進(jìn)程。將創(chuàng)建一個(gè) 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;
- }
- }
- }
添加了兩個(gè)感興趣的字段(TotalMemory、AfterGC),重寫 MessageBytes 屬性以實(shí)現(xiàn)轉(zhuǎn)換(get 和 set 方法位于轉(zhuǎn)換發(fā)生的位置),然后添加一個(gè)默認(rèn)的構(gòu)造函數(shù)(這個(gè)參數(shù)化的構(gòu)造函數(shù)是可選的)。
現(xiàn)在,如果要使用前面的示例,只需更改兩處地方:
發(fā)送消息時(shí),要?jiǎng)?chuàng)建一個(gè) CustomMessage,而不是將值賦給 TotalMemory 和 AfterGC。
- //msg = new Message(. . .); //Instead of this line
- msg = new CustomMessage(GC.GetTotalMemory(false), false);
- ReadWriteResult rwr = mQue.Send(msg, mTimeout);
接收消息時(shí),要?jiǎng)?chuàng)建一個(gè) CustomMessage。
- //msg = new Message();
- msg = new CustomMessage(); //msg still declared as Message
- ReadWriteResult rwr = mQue.Receive(msg, mTimeout);
然后,在 mQue.Receive 返回時(shí)讀取它的屬性。
- //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");
【編輯推薦】