適合C# Actor的訊息執行方式(3):中看不中用的解決方案
在前兩篇文章中,我們瞭解到Erlang中靈活的模式匹配,以及在C#甚至F#中會都遭遇的尷尬局面。那麼現在就應該來設計一個解決方案了,我們如何才能在C#這樣的語言裡順暢地使用Actor模型呢?不僅如此,最好我們還能獲得其它一些優勢。
“訊息”、“協議”和“介面”
Actor模型中的物件如果要進行互動,唯一的手段便是傳送訊息。不同語言/平臺上的訊息有不同的表現形式,但是它們所傳遞的資訊是一致的:
- 做什麼事情
- 做這件事情需要的資料
例如,Erlang中往往會使用Tag Message的格式作為訊息:
{doSomething, Arg1, Arg2, Arg3, ...}
其中,原子doSomthing表示“做什麼”,而後面的ArgN便是一個個的引數,使用Erlang中的模式匹配可以很方便地捕獲訊息中的資料。在C#等語言中,由於並非專為了Actor模型設計,因此一個Message往往只能是一個物件。但是這個物件的職責並沒有減輕,因此我們需要自己處理的事情就多了。我們可能會這樣做:
- 學Erlang的Tag Message,但是這樣會產生大量醜陋的型別轉換操作,並且喪失了靜態檢查功能。
- 為每種訊息建立不同的Message型別,但是這樣會產生大量類型別,每個型別又有各種屬性,非常麻煩。
這兩種做法在上一篇文章裡都有過討論,感興趣的朋友可以再去“回味”一番。那麼,究竟什麼是訊息呢?根據我的理解,“訊息”其實是這麼一種東西:
- “訊息”表示“傳送方”和“接受方”之間的“通訊協議”(例如Erlang中的“模式”)。
- “訊息”表示“傳送方”要“接受方”所做的事情,但是並沒有要求“接受方”需要怎麼做。
- 一個Actor可能會會作為“接受方”遵守多種“通訊協議”。
經過這樣的描述,您是否覺得.NET中有一種東西和“訊息”非常接近?沒錯,那就是“介面”,因為:
- “介面”從概念上講便是一種“協議”。
- “介面”表示“能做什麼”,但沒有限制“怎麼做”。
- 一個Actor可以實現多個介面,即遵守多種協議。
看上去還真是一一對應啊!那麼我們再來深入一步進行對比,“介面”能否傳遞訊息所要表現的資訊?答案也是肯定的:
- 做什麼事情:介面中的一個方法。
- 需要的資料:介面的引數。
也就是說,如之前的那條Erlang訊息,在C#中便可以表示為:
x.DoSomething(arg1, arg2, arg3, ...)
基於這樣的類比,我們發現使用“介面”還可以帶來一個額外的東西,那就是“訊息組”。如Erlang這樣語言,訊息與訊息之間是完全獨立的。.NET中的介面可以包含多個方法,這就是一種“分組”,我們可以利用這種方式來更好地管理有關聯的訊息。此外,利用.NET中的訪問限制符(public,internal等)還可以實現訊息的公開和隱藏。而且因為介面的引數是強型別的,所以可以得到編譯期的檢查,也可以享受編輯工具的程式碼提示及重構……C#程式設計裡的種種優勢似乎我們一個都沒有拉下。
看似美好的實現
等一下,介面只是一種“協議”,但是“訊息”還必須是一個實體,一個物件,並且“攜帶”了這個協議才能在Actor之間傳遞啊。這個物件除了攜帶協議所需要的資料以外,還要能夠告訴接受方究竟該“操作什麼”。“操作”帶上“資料”,於是我就想到了“委託”。例如,如果我們想要傳送一個“協議”,叫做IDoHandler,那麼我們便可以構造一個Action
Action<IDoHandler> m = x => x.Do(0, 1, 2, ...);
好,那麼我們還是用乒乓測試來嘗試一番。我們知道,乒乓測試會讓Ping物件和Pong物件相互傳送訊息,我們各使用一個“訊息組”,也就是“介面”來定義訊息:
public interface IPongMessageHandler { } public interface IPingMessageHandler { }
那麼,Ping和Pong兩個Actor型別又該如何定義呢?我們知道,Ping需要處理Pong發來的訊息,因此它需要實現IPongMessageHandler介面,並且需要接受型別為Action
public class Ping : Actor<Action<IPongMessageHandler>>, IPongMessageHandler { private int m_count; public Ping(int count) { this.m_count = count; } protected override void Receive(Action<IPongMessageHandler> message) { message(this); } ... } public class Pong : Actor<Action<IPingMessageHandler>>, IPingMessageHandler { protected override void Receive(Action<IPingMessageHandler> message) { message(this); } ... }
從程式碼上看,實際操作中我們並不需要讓Ping或Pong直接繼承Handler介面,只要最終提供一個物件給message執行即可。嚴格說來,“介面”只是一個“訊息組”,具體的“訊息”還是要落實到介面中的方法。定義了Ping和Pong之後,我們便可以明確介面中的方法了(確切地說,是明確了方法的引數):
public interface IPongMessageHandler { void Pong(Pong pong); } public interface IPingMessageHandler { void Ping(Ping ping); void Finish(); }
使用了介面,自然就要提供方法的實現了。我們先從典型而簡單的Pong物件看起:
public class Pong : Actor<Action<IPingMessageHandler>>, IPingMessageHandler { ... #region IPingMessageHandler Members void IPingMessageHandler.Ping(Ping ping) { Console.WriteLine("Pong received ping"); ping.Post(h => h.Pong(this)); } void IPingMessageHandler.Finish() { Console.WriteLine("Finished"); this.Exit(); } #endregion }
原本需要在得到訊息之後,根據訊息的內容作出不同的響應。而現在,訊息會被自動轉發為介面中的方法呼叫,我們只需要實現特定的方法即可。在Ping方法中,我們會得到一個Ping型別的物件——於是我們再向它回覆一個訊息。訊息的型別是Action
Ping類也只需要實現IPongMessageHandler即可,只是這段邏輯“略顯複雜”:
public class Ping : Actor<Action<IPongMessageHandler>>, IPongMessageHandler { ... public void Start(Pong pong) { pong.Post(h => h.Ping(this)); } #region IPongMessageHandler Members void IPongMessageHandler.Pong(Pong pong) { Console.WriteLine("Ping received pong"); if (--this.m_count > 0) { pong.Post(h => h.Ping(this)); } else { pong.Post(h => h.Finish()); this.Exit(); } } #endregion }
收到Pong訊息之後,將count減1,如果還大於0,則回覆一個Ping訊息,否則就回復一個Finish並退出。最後啟動乒乓測試:
new Ping(5).Start(new Pong());
由於使用了介面作為訊息的協議,因此無論是編輯器還是編譯器都可以給我們足夠的支援。同時,對於訊息的處理也無須如上一篇文章那樣不斷進行判斷和型別轉換,程式碼可謂流暢不少。
致命的缺陷
雖說沒有完美的東西,但目前的缺陷卻是致命的。
在實際使用過程中,訊息的“傳送方”和訊息的“接收方”應該完全無關,它們互不知道對方具體是誰,只應該基於“協議”,也就是“介面”來實現。可惜在上面這段程式碼中,很多東西都被“強橫”地限制住了。例如,Ping訊息會附帶一個ping物件作為引數,ping物件會等待一個Pong訊息。但是,傳送Ping訊息(並等待Pong訊息)的一方很可能是各種型別的Actor,不一定是Ping型別。有朋友可能會說,那麼我們把IPingMessageHandler的Ping方法的簽名改成這樣,不就可以了嗎?
void Ping(Actor<Action<IPongMessageHandler>> ping)
是的,此時的ping,的確是在“等待Pong訊息的Actor物件”。但是,這意味著ping物件它也只能是這個指明的Actor型別了。在實際使用過程中,這幾乎是不可能的事情。因為一個Actor很可能會接受各種訊息,它很難做到“一心一意”。因此這篇文章所提出的做法,幾乎只能滿足如乒乓測試這樣簡單的Actor模型使用場景。我們必須改變。
改變的方式有不少,從“向弱型別妥協”到“利用.NET 4.0中的協變/逆變”,都可以滿足不同的場景——不過我們還是下次再說吧。
F#的實現
本文描述的方式也可以運用在在F#中。首先自然還是介面的定義:
type IPingMessageHandler = abstract Ping : Ping -> unit abstract Finish : unit -> unit and IPongMessageHandler = abstract Pong : Pong -> unit
以上便是F#中定義介面的方式,與C#相比更為簡潔。接著便是Ping型別的實現:
and Ping() = inherit (IPongMessageHandler -> unit) Actor() let mutable count = 5 override self.Receive(message) = message self member self.Start(pong : Pong) = pong << fun h -> self |> h.Ping interface IPongMessageHandler with member self.Pong(pong) = printfn "Ping received pong" count if(count > 0) then pong << fun h -> self |> h.Ping else pong << fun h -> h.Finish() self.Exit()
Pong型別的實現則更為簡單:
and Pong() = inherit (IPingMessageHandler -> unit) Actor() override self.Receive(message) = message self interface IPingMessageHandler with member self.Ping(ping) = printfn "Pong received ping" ping << fun h -> self |> h.Pong member self.Finish() = printfn "Finished" self.Exit()
啟動乒乓測試:
(new Pong()) |> (new Ping()).Start;
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-609712/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 適合C# Actor的訊息執行方式(2):C# Actor的尷尬C#
- 一種適合C# Actor的訊息執行方式(中):C# Actor的尷尬C#
- 適合C# Actor的訊息執行方式(1):Erlang中的模式匹配C#模式
- 一種適合C# Actor的訊息執行方式(上):Erlang中的模式匹配C#模式
- 適合C# Actor的訊息執行方式(5):一個簡單的網路爬蟲C#爬蟲
- RocketMQ訊息丟失解決方案:事務訊息MQ
- .NET混合開發解決方案6 檢測是否已安裝合適的WebView2執行時WebView
- RabbitMQ:訊息丟失 | 訊息重複 | 訊息積壓的原因+解決方案+網上學不到的使用心得MQ
- java nio訊息半包、粘包解決方案Java
- jDeveloper執行慢解決方式Developer
- 史上最全解析Android訊息推送解決方案Android
- EMQ 解決方案之雲平臺物聯網訊息佇列解決方案MQ佇列
- 個推百億級訊息推送的優先順序解決方案
- TortoiseSVN 執行清理( cleanUp )失敗的解決方案
- Python 執行js的2種解決方案PythonJS
- Arch Linux 下執行 QQ 的解決方案Linux
- C++ 多執行緒框架(3):訊息佇列C++執行緒框架佇列
- 適合證券公司的跨網傳輸解決方案,瞭解一下!
- Windows下使用python庫 curses遇到錯誤訊息的解決方案WindowsPython
- 詳解 RxJava 的訊息訂閱和執行緒切換原理RxJava執行緒
- 多執行緒的安全問題及解決方案執行緒
- 多執行緒-多執行緒方式3的求和案例執行緒
- 如何選擇最適合的採購付款 (P2P) 解決方案?
- Vue 元件通訊的解決方案Vue元件
- 多執行緒-多執行緒方式3的思路及程式碼實現:方式3依賴於執行緒池存在的執行緒
- 訊息佇列——數十萬級訊息的消費方案佇列
- ASP.NET 在IIS上執行不了的解決方案ASP.NET
- Node出錯導致執行崩潰的解決方案
- c# 多執行緒的幾種方式 【轉載】C#執行緒
- shardingJdbc分表執行批次update不支援的解決方式JDBC
- 執行create table as 報ora-600的錯誤的解決方案
- 可執行資訊和傳送訊息的服務?
- 為什麼Actor模型是高併發事務的終極解決方案?模型
- RocketMQ訊息丟失解決方案:同步刷盤+手動提交MQ
- ChatGPT 訊息發不出去了?我找到解決方案了ChatGPT
- 淺談-web螢幕適配的解決方案Web
- [iOS]iOS 7的Navigation適配解決方案iOSNavigation
- C#的訊息處理方法 (轉)C#