通道狀態機
通道和通道工廠擁有相同的特性,這些特性獨立於運行時功能。其中最重要 的特性之一就是他們擁有公共的狀態機。WCF程序裡的每個通道和通道工廠都有 一個預定義的狀態集合和一個預定義的方法集合,這些方法會控制通道和通道工 廠在這些狀態之間轉換。
ICommunicationObject接口
在面向對象層次上,WCF類型系統強制實現了各個通道共用一個狀態機,方式 就是就是所有的通道和通道工廠都實現 System.ServiceModel.ICommunicationObject接口。這個接口看起來也非常簡單 :
public interface ICommunicationObject {
event EventHandler Closed;
event EventHandler Closing;
event EventHandler Faulted;
event EventHandler Opened;
event EventHandler Opening;
void Abort();
IAsyncResult BeginClose(AsyncCallback callback, object state);
IAsyncResult BeginClose(TimeSpan timeout, AsyncCallback callback,
Object state);
IAsyncResult BeginOpen(AsyncCallback callback, object state);
IAsyncResult BeginOpen(TimeSpan timeout, AsyncCallback callback,
Object state);
void Close();
void Close(TimeSpan timeout);
void EndClose(IAsyncResult result);
void EndOpen(IAsyncResult result);
void Open();
void Open(TimeSpan timeout);
CommunicationState State { get; }
}
備注:本節裡為了簡潔起見,這裡我只會把實現ICommunicationObject接口 的類型作為通道,盡管通道工廠也實現了這個接口。
我們先看一下方法。為了打開、關閉和終止通道,ICommunicationObject聲 明了幾個方法。這裡重載了幾個接受TimeSpan參數的異步Open和Close方法。理 論上,無參的Open和Close方法會阻塞,直到通道最終打開或者關閉。實際上, 這樣不好,重載的方法帶有一個TimeSpan參數,它表示願意等待的通道對象打開 或者關閉的時間。因為調用者不應該無限期等待通道打開或者關閉,所以無參的 Open和Close方法應該傳遞一個默認TimeSpan值去調用重載後的方法。
ICommunicationObject接口還定義了符合微軟異步編程模型的BeginOpen和 BeginClose方法。因為打開和關閉一個通道可能要涉及到I/O,因此使用異步編 程模型是個不錯的方法。這樣做意味著程序可以在打開和關閉通道的時候,使用 線程池來實現資源的高效管理和線程調用,而不需要阻塞進程。這裡重載了允許 傳遞TimeSpan參數的BeginOpen和BeginClose方法來,和其它的方法很像,但是 不同的是這些方法可以設置等待的時間。在打開和關閉通道的時候,我推薦使用 ICommunicationObject接口裡定義的異步方法。
ICommunicationObject接口也定義了一個CommunicationState 類型的只讀屬 性。這個成員是為了查詢通道目前的狀態。你會在下一節 “CommunicationObject類型”裡學習更多的通道狀態機的內容。現在,我們來 看一下CommunicationState狀態值,如下所示,是個枚舉類型:
public enum CommunicationState {
Created,
Opening,
Opened,
Closing,
losed,
Faulted
}
ICommunicationObject接口也定義了幾個事件。如其它的.NET Framework事 件一樣,這些事件是其它對象接受通道狀態改變通知的一種方法。事件的名字與 狀態類型CommunicationState相關。我們會在下一節詳細學習這些事件。
CommunicationObject類型
就其本身而言,實現ICommunicationObject接口對於通道或通道工廠的一致 狀態轉換並不能帶來什麼幫助。相反,它保證了所有的通道和通道工廠擁有相同 的成員。實際上,繼承一個共同的基類能確保所有類型都保持行為上的一致性, 而不是繼承一個接口。抽象類型System.ServiceModel.Channels. CommunicationObject就是為了滿足這個需求。
圖6-2:CommunicationObject裡內嵌的通道狀態機
備注:本節裡為了簡潔起見,我只會把提到的CommunicationObject的子類型 作為通道,盡管其它的類型也繼承了這個類型。
CommunicationObject是所有通道的基類型,而且CommunicationObject類型 實現了ICommunicationObject接口。Open、Close和Abort方法保證了通道狀態可 以按照一種連續的方式裡轉換。如圖6-2. CommunicationObject不僅僅實現了 ICommunicationObject接口,它也會在適當的時機激活事件、調用抽象和虛方法 ,它同時也提供了實現錯誤處理一致性幫助方法。下一節裡會講述 CommunicationObject控制通道在不同狀態間的轉換過程。
CommunicationObject子類型
實際上,CommunicationObject的繼承類型都應該使用CommunicationObject 定義的狀態機和它的錯誤處理成員,當然為了滿足特定需求,也可以加入自己的 實現。對於任何類型,盲目地繼承一個基類型並不能確保恰當地使用基類型的功 能。在構建一個通道的時候,非常重要的一點就是在合適的地方添加功能和正確 調用基類型的方法。
CommunicationObject定義了幾個虛方法。當繼承這個類型的時候,需要子類 型重寫這些方法,這裡十分重要的一點就是,繼承類型調用基類 CommunicationObject 的成員,因為基類提供了控制狀態轉換和激活事件的實現 。調用失敗的話意味著狀態不會正確地轉換,當然也不會激活正確的事件,通道 也沒什麼用處了。 CommunicationObject的繼承類型不是必須要重寫這些方法。 只是當這些繼承類型需要有自己特別的實現的時候才會重寫這些成員。
下面代碼展示了CommunicationObject的虛方法,以及如何重寫這些方法:
public abstract class CommunicationObject : ICommunicationObject {
// virtual methods shown, others omitted
protected virtual void OnClosed();
protected virtual void OnClosing();
protected virtual void OnFaulted();
protected virtual void OnOpened();
protected virtual void OnOpening();
}
sealed class CommunicationObjectDerivedType : CommunicationObject {
// other methods omitted for clarity為了簡潔,省略了一些方法
protected override void OnClosed() {
// implementation can occur before or after
// the call to the base implementation
base.OnClosed();
}
protected override void OnClosing() {
// implementation can occur before or after
// the call to the base implementation
//代碼會在調用基類的實現以前或後面執行
base.OnClosing();
}
protected override void OnOpened() {
// implementation can occur before or after
// the call to the base implementation
base.OnOpened();
}
protected override void OnOpening() {
// implementation can occur before or after
// the call to the base implementation
base.OnOpening();
}
protected override void OnFaulted() {
// implementation can occur before or after
// the call to the base implementation
base.OnFaulted();
}
}
CommunicationObject同樣定義了幾個抽象成員,它們是通道執行特別工作任 務的主要方式。下面的代碼列舉這些抽象成員:
public abstract class CommunicationObject : ICommunicationObject {
// abstract members shown, others omitted
protected abstract void OnOpen(TimeSpan timeout);
protected abstract IAsyncResult OnBeginOpen(TimeSpan timeout,
AsyncCallback callback, Object state);
protected abstract void OnEndOpen(IAsyncResult result);
protected abstract void OnClose(TimeSpan timeout);
protected abstract IAsyncResult OnBeginClose(TimeSpan timeout,
AsyncCallback callback, Object state);
protected abstract void OnEndClose(IAsyncResult result);
protected abstract void OnAbort();
protected abstract TimeSpan DefaultCloseTimeout { get; }
protected abstract TimeSpan DefaultOpenTimeout { get; }
}
上面代碼裡最讓我們感覺驚訝的應該就是DefaultCloseTimeout和 DefaultOpenTimeout屬性。規則上,當決定調用哪個成員方法的時候,我們通常 會選擇一個帶有TimeSpan參數的方法。很顯然,這樣可以控制超時的時間范圍。 事實上,沒有TimeSpan參數的方法,也是調用帶有TimeSpan參數的方法。這時, 使用的是默認的DefaultOpenTimeout和DefaultClosedTimeout值。
OnOpen、OnClose和OnAbort方法以及它們的同名方法,都是 CommunicationObject的繼承類型裡定義對象初始化和資源回收代碼的地方。比 如,如果你要實現一個自定義通道,這個通道使用用戶報文協議(UDP),初始 化socket連接的代碼要放在OnOpen和OnBeginOpen方法裡。同樣,關閉socket連 接的代碼也會放在OnClose、OnBeginClose和OnAbort方法裡。
第一次接觸通道和通道狀態機的時候,容易令人迷惑的地方之一,就是 CommunicationObject如何與其繼承類型交互。我個人認為,理解這個交互過程 是理解通道如何工作的第一步。後面的章節裡會介紹CommunicationObject如何 與其子類型在Open、Close、Abort和Fault方法中協調工作。為了說明以上這些 內容,這裡簡要定義了一個上下文環境,代碼如下:
sealed class App {
static void Main() {
MyCommunicationObject myCommObject = new MyCommunicationObject();
// method invocations here這裡調用方法
}
}
sealed class MyCommunicationObject : CommunicationObject {
// implementatation omitted for brevity簡化起見省略代碼實現部 分
}
Open和BeginOpen方法
前面提到,CommunicationObject定義了Open 和BeginOpen方法來打開 CommunicationObject的子類型。接下來本節裡要描述的就是打開一個 CommunicationObject子類型發生的事情:
MyCommunicationObject myCommObject = new MyCommunicationObject();
myCommObject.Open();
CommunicationObject:檢查狀態是否可以轉換為Open
如果狀態屬性不是CommunicationObject.Created ,調用Open和 BeginOpen 方法,就會拋出一個異常。CommunicationObject類型會調用 ThrowIfDisposedOrImmutable保護方法來檢查狀態。如果CommunicationState是 CommunicationState.Opened 或CommunicationState.Opening狀態,調用Open和 BeginOpen方法都會拋出InvalidOperationException異常。同樣,如果State是 CommunicationState.Closed 或CommunicationState.Closing,調用Open和 BeginOpen方法也會拋出ObjectDisposedException異常。值得注意的是,這些狀 態檢查都是以線程安全的方式進行的。下面代碼給出了 CommunicationObject.Open方法的簡單實現:
lock (this.thisLock){
// check the state, throw an exception if transition is not OK
//檢查狀態,如果轉換失敗,就會拋出異常
this.ThrowIfDisposedOrImmutable();
// other implementation shown in the next section 下一節會介紹具體的實現
}
CommunicationObject:如可以可以狀態變為Opening
如果當前狀態是CommunicationState.Created, State屬性會變為 CommunicationState.Opening。下面代碼演示了CommunicationObject.Open方法 如何把狀態轉換為CommunicationState.Opening的:
lock (this.thisLock){
// check the state, throw an exception if transition is not OK
//檢查狀態,如果轉換失敗,就會拋出異常
this.ThrowIfDisposedOrImmutable();
// transition the CommunicationState
this.state = CommunicationState.Opening;
}
MyCommunicationObject:調用虛方法OnOpening
如果CommunicationState的狀態可以正常的轉換為Opening, CommunicationObject.Open 方法會調用CommunicationObject.OnOpening虛方法 。如果CommunicationObject的子類型重寫了這個方法的話,就會首先調用子類 型的OnOpening方法。如前所述,子類型裡重寫OnOpening方法必須調用基類型 CommunicationObject 的OnOpening方法。
CommunicationObject:激活Opening事件,調用委托方法
調用CommunicationObject 類型的OnOpening方法會激活Opening 事件,並調 用事件引用的委托方法。這也是子類型為什麼要調用CommunicationObject 的 OnOpening方法的原因之一。如果這個過程失敗,CommunicationObject.Open會 拋出一個 InvalidOperationException異常。
MyCommunicationObject:調用OnOpen虛方法
如果OnOpening沒有拋出異常,CommunicationObject.Open 方法會調用子類 型的OnOpen方法。因為CommunicationObject定義抽象的OnOpen方法,子類型必 須實現這個方法。如前面提到的,這就是那個包含大量CommunicationObject子 類型初始化工作的方法。
MyCommunicationObject:調用OnOpened虛方法
如果OnOpen沒有拋出異常,CommunicationObject.Open 方法會調用OnOpened 虛方法。如果子類型實現了OnOpened方法,就會調用子類型裡重寫的方法。就 OnOpening方法而言,子類型調用 CommunicationObject.OnOpened是非常重要的 。如果調用失敗會導致CommunicationObject.Open方法拋出一個 InvalidOperationException異常。
CommunicationObject:狀態轉換為Opened
CommunicationObject.OnOpened方法,除了別的以外,會把 CommunicationObject的State屬性值修改為CommunicationState.Opened。這裡 狀態輪換的前一個狀態必須是CommunicationState.Opening。
CommunicationObject:激活Opened事件,調用委托方法
在狀態轉換為Opened以後,CommunicationObject.OnOpened方法會激活 Opened事件,因此可以調用綁定的委托方法。
Close和Abort方法
CommunicationObject類型暴露了可以銷毀對象的方法。通常,Close和 BeginClose方法可以以一種優雅的方式CommunicationObject關閉對象,而Abort 方法則會立即關閉對象。這裡Close方法包含一個異步實現,而Abort方法沒有。 原因是兩者的角色不同。例如,在正常情況下關閉對象是通過調用Close (或 BeginClose)方法實現的,當關閉對象的時候,CommunicationObject還能夠執行 I/O操作。為了說明這個問題,我們可以想一下在使用WS-ReliableMessaging (WS-RM)可靠消息傳遞的時候,調用Close方法,消息會繼續發送、這時,Close 方法會讓負責WS-RM可靠消息傳遞的通道向另外的消息接受者發送一個 TerminateSequence消息。換句話說,Close方法能夠觸發I/O。
另外一方面,調用Abort方法會立即關閉CommunicationObject通信對象並執 行最小的I/O操作。因此,不需要任何的異步Abort方法的實現。值得一提的是 Abort方法也不可以接受TimeSpan參數,而Close方法是可以的。
CommunicationObject和其子類型之間調用Close或BeginClose方法的協作模 式與調用Open方法的協作模式十分相似。如前所述,調用 CommunicationObject.Open方法會引起OnOpening、OnOpen和OnOpened方法的執 行。同樣,調用CommunicationObject.Close方法,也會引起OnClosing、 OnClose和OnClosed方法的執行。下面代碼簡要說明了.NET Framework如何定義 CommunicationObject.Close方法:
public void Close(TimeSpan timeout){
// only general implementation shown
this.OnClosing();
this.OnClose(timeout);
this.OnClosed();
}
此外,與激活Opening和Opened 事件的方式十分相似,CommunicationObject 也也會激活Closing和 Closed事件。
Abort方法也會調用其它方法。下面代碼塊簡要說明了.NET Framework如何定 義CommunicationObject.Abort方法:
public void Abort(){
// only general implementation shown
this.OnClosing();
this.OnAbort(); // only difference from Close
this.OnClosed();
}
如上代碼所示,Abort方法與調用Close方法裡執行的代碼十分相似。 OnClosing和OnClosed會分別激活Closing和Closed事件。效果上,Abort方法與 Close方法擁有一些相同的代碼執行路徑,並且會激活相同的事件。
記住CommunicationObject對象的主要工作就是維護一個一致的狀態機,它說 明了,Close和Abort方法執行路徑的改變,都是基於正在關閉或者終止的對象的 State屬性。為了說明這個問題,思考一下我們調用一個狀態為 CommunicationState.Created的實例的Close方法。如果Open方法沒被調用, Close和Abort方法的執行路徑還有任何區別嗎?記住,CommunicationObject的 初始化工作都是在調用Open 或BeginOpen方法時完成的。在其中一個任何一個方 法執行前,CommunicationObject對象都只是一個托管堆上的普通對象而已、在 pre-open狀態之前,CommunicationObject.Close方法和 CommunicationObject.Abort方法執行的是相同的工作。可是,在Open或 BeginOpen方法執行後,CommunicationObject對象可能會引用一些對象,比如連 接的socket,而CommunicationObject. Close方法和 CommunicationObject.Abort方法就執行不同的任務。表6-1描述了 CommunicationObject的狀態如何影響Close和Abort方法的執行過程。當你看這 個表的時候,記住Close是一種關閉CommunicationObject對象的優雅方式,而 Abort方法則顯得過於粗暴。
表6-1: CommunicationState、Close和Abort 的對應關 系 State屬性 Close Abort CommunicationState.Created 調用 Abort 正常中止 CommunicationState.Opening 調用Abort 正常中止 CommunicationState.Opened 正常關閉 正常中止 CommunicationState.Closing 無操作 正常中止 CommunicationState.Closed 無操作 無操作
Fault方法
受保護的Fault方法也是一種關閉CommunicationObject 對象的方式,但它不 屬於ICommunicationObject接口。因為對於外部調用者來說,它是不可見的,所 以它只適用於 CommunicationObject對象的子類型,當這些子類型發現任何錯誤 的時候,都可以立即關閉通道。調用Fault方法會把State屬性轉換為 CommunicationState.Faulted,並且調用OnFaulted虛方法,因此 CommunicationObject的子類型可以在重寫的虛方法裡定義自己的行為。大部分 情況,OnFaulted方法都會調用Abort方法。
CommunicationObject堆棧
記住CommunicationObject類型是所有通道和通道工廠的基類型。記住通道和 通道工廠一般會組織為一個堆棧,並且只有頂部的通道對於調用者是可見的。在 概念上,堆棧的組織順序可以通過下面的類型查看:
internal sealed class MyCommunicationObject : CommunicationObject {
private CommunicationObject _inner;
internal MyCommunicationObject(CommunicationObject inner){
this._inner = inner;
}
// other implementation omitted for brevity為了簡潔起見,省 略其它代碼
}
MyCommunicationObject類型繼承自CommunicationObject,它受 CommunicationObject裡定義的狀態機的約束。確切地說, MyCommunicationObject對象有責任通過狀態機與 _inner成員變量同步轉換。例 如,如果調用者調用了MyCommunicationObject的Open方法,則 MyCommunicationObject.Open的實現代碼一定會調用inner成員變量的Open方法 :
internal sealed class MyCommunicationObject : CommunicationObject {
private CommunicationObject _inner;
internal MyCommunicationObject(CommunicationObject inner){
this._inner = inner;
} protected override void OnOpen(TimeSpan timeout) {
// MyCommunicationObject.OnOpen implementation here
// ...
// Call Open on the inner member variable調用inner 成員變量的Open方法
// NOTE: may want to reduce timeout
_inner.Open(timeout);
}
// other implementation omitted for brevity為了簡潔起見 ,省略其它代碼
}
當這樣組織通道堆棧的時候,MyCommunicationObject.Open方法的調用者就 不需要知道堆棧裡所有的CommunicationObject 節點,它們都會通過相同的狀態 機來以同步的方式實現狀態轉換。為了徹底說明問題,這裡需要著重指出的是, 在 MyCommunicationObject.OnOpen 方法之前或者之後調用_inner.Open方法差 別不大。不過,實際上,通常都是在方法結束的時候才調用的。另外,這裡也許 要調整傳遞給inner 成員變量的TimeSpan參數的值,來設置打開通道的超時范圍 。
【老徐備注】
1. CommunicationObject 類:
為系統中所有面向通信的對象(包括通道和通道工廠)公用的基本狀態機提 供通用的基實現。
2. CommunicationObject 方法: