不論是哪種操做,在任意時刻均可能出現不可預期的錯誤。問題在於咱們應該如何將錯誤報告給客戶端。異常和異常處理機制是與特定技術緊密結合的,不能跨越邊界的。此外,若是有客戶端來處理錯誤,一定會致使耦合度增長。一般,錯誤處理應該是本地的實現細節,並不會影響到客戶端。在設計良好的應用程序中,服務應該是被封裝的,客戶端沒法知道有關錯誤的消息。設計良好的服務應儘量是自治的,不能依賴客戶端去處理或恢復錯誤。任何非空的錯誤通知都應該是客戶端與服務端之間契約交互的一部分。編程
WCF中錯誤的表現。當表明某個客戶端的服務調用致使異常時,並不會借宿宿主進程,其它客戶端仍然能夠訪問該服務。託管在相同進程中的其它服務也不會受到影響。因此,當一個未經處理的異常離開服務範圍時,分發器會捕獲它,並將它序列化到返回消息中傳遞給客戶端。當返回消息到達代理時,代理會在客戶端拋出一個異常。這一方式爲每一個WCF服務提供了進程級的隔離。客戶端與服務端可以共享一個進程,但對於錯誤的處理卻徹底是獨立的。惟一的例外是可以致使.NET崩潰的關鍵錯誤,如堆棧溢出,纔會影響到宿主進程。安全
錯誤隔離是WCF的三個關鍵錯誤解耦特性之一。第二個是錯誤屏蔽,第三個是通道故障。網絡
當客戶端試圖調用服務時,實際上可能會遭遇三種錯誤類型。spa
第一種錯誤類型是通訊錯誤。如網絡故障,地址錯誤,宿主進程沒運行等等。客戶端的通訊錯誤表現爲CommunicationException異常或其子類異常,如EndpointNotFoundException。設計
第二種錯誤類型與代理和通道的狀態有關。這種類型存在不少可能的異常。如,試圖訪問已經關閉的代理,就會致使ObjectDisposedException異常;契約和綁定的安全級別不想匹配時,就會致使InvalidOperationException異常等等代理
第三種錯誤類型源於服務調用。這種錯誤節能是服務拋出異常,也多是服務在調用其它對象或資源經過內部調用拋出的異常。code
客戶端所關注的就是錯誤發生了,對於大多數客戶端而言,最佳實踐就是簡單地讓異常在調用鏈中向上傳遞。最頂端的客戶端會捕獲異常,但並非爲了處理,而僅僅是爲了避免讓程序忽然關閉。對象
設計良好的客戶端應該永遠都不考慮實際的錯誤,WCF對此進行了強制要求。出於對封裝和解耦的考慮,在默認狀況下,全部的服務拋出的異常老是以FaultException類型到達客戶端。blog
[...] public class FaultException : CommunicationException {...}若是要解耦客戶端與服務,就應該對全部的服務異常一視同仁。客戶端對服務端直到的越少,則二者之間關係的解耦才越完全。繼承
在傳統的.NET編程中,客戶端能夠捕獲異常,並繼續調用對象。這樣的類和接口能夠定義爲:
interface IMyContract { void MyMethod(); } class MyClass : IMyContract {...}問題是客戶端捕獲了對象拋出的異常後,它仍是能夠被再次調用:
IMyContract obj = new MyClass(); try { obj.MyMethod(); } catch {}obj.MyMethod();
這是.NET平臺的一個重要缺陷。客戶端怎麼能夠知道可能拋出異常,仍然會使用它呢。
在經典的.NET編程模型中,開發人員必須在每一個對象中委會一個標誌:在拋出異常以前或在拋出異常以後設置該標誌,而後在公開方法內檢查該標誌,若是對象是在拋出異常以後被調用,則拒絕使用該對象。固然,這很繁瑣並且乏味。
WCF自動實現了這一最佳實踐。若是服務具備傳輸會話,則任何未經處理的異常(保存在繼承子FaultException類中)都會致使通道發生錯誤(代理的狀態就會被修改成CommunicationState.Faulted),這樣就避免了客戶端使用該代理,而對象也能夠隱藏在異常以後。換句話說,對於以下的服務和代理的定義,最直接的解決方案就是客戶端不要在拋出異常以後使用WCF代理。若是具備傳輸會話,客戶端甚至不能關閉代理。
在拋出異常以後,客戶端惟一能夠安全執行的就是取消代理,或者防止其它的客戶端使用代理:
MyContractClient proxy = new MyContractClient(); try { proxy.MyMethod(); } catch { proxy.Abort(); }如今的問題是,對於每一個方法調用,你都必須重複這些代碼。最好能把這些代碼封裝在代理類中。
class MyContractClient : ClientBase<IMyContract>,IMyContract { public void MyMethod() { try { Channel.MyMethod(); } Catch { Abort(); throw; } } }
關閉代理和using語句
這裏反對使用using語句關閉代理。其緣由在於,若是存在傳輸會話,則任何服務端的異常都會致使通道故障。一旦通道出現故障,試圖釋放代理就會拋出CommunicationObjectFaultException異常,所以,在using語句以後的代碼永遠不會調用,在using語句內捕獲了全部異常也是如此。
using(MyContrctClient proxy = new MyContractClient()) { try { proxy.MyMethod(); } catch {} }這就下降了代碼的可讀性,並可能致使BUG,由於代碼的執行結果並不是開發人員所指望的那樣。惟一的解決之道使用try/catch語句將using語句包裹起來:
try { using(MyContrctClient proxy = new MyContractClient()) { try { proxy.MyMethod(); } catch {} } } catch {}固然這比Close()方法要好得多。出現異常時,異常會跳出對Close()的調用:
MyContrctClient proxy = new MyContractClient() proxy.MyMethod(); proxy.Close();固然,你也能夠捕獲異常,這樣的代碼具備可讀性:
MyContrctClient proxy = new MyContractClient() try { proxy.MyMethod(); proxy.Close(); } catch { proxy.Abort(); }異常和實例管理
若是服務被配置爲單調模式或會話模式,則客戶端異常就不會訪問同一個實例。固然,這自己就是單調服務的特色。對於會話服務,則是通道出現故障,終止了傳送會話所形成的後果。單例服務並不會被終止,而會繼續運行。若是沒有傳輸會話,客戶端會繼續使用代理鏈接單例對象。通道出現故障,客戶端仍是可以建立一個新的代理實例,從新鏈接單例服務。
參考:《WCF服務編程 第三版》