程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WCF技術剖析之十:調用WCF服務的客戶端應該如何進行異常處理

WCF技術剖析之十:調用WCF服務的客戶端應該如何進行異常處理

編輯:關於.NET

在前面一片文章(服務代理不能得到及時關閉會有什麼後果?)中,我們談到及時關閉服務代理(Service Proxy)在一個高並發環境下的重要意義,並闡明了其根本原因。但是,是否直接調用ICommunicationObject的Close方法將服務代理關閉就萬事大吉了呢?事情遠不會這麼簡單,這其中還會涉及關於異常處理的一些操作,這就是本篇文章需要討論的話題。

一、異常的拋出與Close的失敗

一般情況下,當服務端拋出異常,客戶客戶端的服務代理不能直接關閉,WCF在執行Close方法的過程中會拋出異常。我們可以通過下面的例子來證實這一點。在這個例子中,我們依然沿用計算服務的例子,下面是服務契約和服務實現的定義:

   1: using System.ServiceModel;
2: namespace Artech.ExceptionHandlingDemo.Contracts
3: {
4: [ServiceContract(Namespace = "urn:artech.com")]
5: public interface ICalculator
6: {
7: [OperationContract]
8: int Divide(int x, int y);
9: }
10: }
1: using System;
2: using System.ServiceModel;
3: using Artech.ExceptionHandlingDemo.Contracts;
4: namespace Artech.ExceptionHandlingDemo.Services
5: {
6: class CalcualtorService : ICalculator { public int Divide(int x, int y) { return x / y; } }
7: }

為了確保服務代理的及時關閉,按照典型的編程方式,我們需要采用try/catch/finally的方式才操作服務代理對象,並把服務代理的關閉放在finally塊中。WCF服務在客戶端的調用程序如下所示:

   1: using System;
2: using System.ServiceModel;
3: using Artech.ExceptionHandlingDemo.Contracts;
4: namespace Client
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: using (ChannelFactory<ICalculator> channelFatory = new ChannelFactory<ICalculator>(new WSHttpBinding(), "http://127.0.0.1:3721/calculatorservice"))
11: {
12: ICalculator calcultor = channelFatory.CreateChannel(); try
13: {
14: calcultor.Divide(1, 0);
15: }
16: catch (Exception ex) { Console.WriteLine(ex.Message); }
17: finally
18: {
19: (calcultor as ICommunicationObject).Close();
20: }
21: }
22: }
23: }
24: }

由於傳入的參數為1和0,在服務執行除法運算的時候,會拋出DividedByZero的異常。當服務端程序執行到finally塊中對服務代理進行關閉的時候,會拋出如下一個CommunicationObjectFaultedException異常,提示SerivceChannel的狀態為Faulted,不能用於後續Communication。

二、原理分析

要解釋具體的原因,還得從信道(Channel)的兩種分類形式說起。在上面一篇文章中,我們就談到過:WCF通過信道棧實現了消息的編碼、傳輸及基於某些特殊功能對消息的特殊處理,而綁定對象是信道棧的締造者,不同的綁定類型創建出來的信道棧具有不同的特性。就對會話的支持來講,我們可以將信道分為以下兩種:

會話信道(Sessionful Channel):會話信道確保客戶端和服務端之間傳輸的消息能夠相互關聯,但是信道的錯誤(Fault)會影響後續的消息交換;

數據報信道(Datagram Channel):即使在同一個數據報信道中,每次消息的交換都是相互獨立,信道的錯誤也不會影響後續的消息交換。

由於上面的例子中,我們采用了WsHttpBinding,所以在默認條件下創建的信道(Channel)是會話信道(Sessionful Channel)。異常拋出後,當前信道的狀態將變成Faulted,表示信道出現錯誤。錯誤的信道將不能繼續用於後續的通信,即使是調用Close方法試圖將其關閉也不行。

也就是說異常導致信道錯誤(Faulted)的特性僅僅對於會話信道而言,而對於數據報信道,則沒有這樣的問題。對於WsHttpBinding在如下兩種情況下下具有創建會話信道的能力:

采用任何一種非None的SecurityMode

采用ReliableSession

再默認的情況下,WsHttpBinding采用的SecurityMode為Message,所以其創建的信道是會話信道。如果我們將其SecurityMode設為None,則在執行Close方法的時候則不會拋出任何異常(而實際上,服務代理的關閉與否對於數據報信道來講,沒有任何意義)。具體實現如下面的代碼所示:

   1: using System;
2: using System.ServiceModel;
3: using Artech.ExceptionHandlingDemo.Contracts;
4: namespace Client
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: using (ChannelFactory<ICalculator> channelFatory = new ChannelFactory<ICalculator>(new WSHttpBinding(SecurityMode.None), "http://127.0.0.1:3721/calculatorservice"))
11: { //......
12: }
13: }
14: }
15: }
16:

三、Close() V.S. Abort()

在這種情況下,一般會調用另一個方法:Abort,強行中斷當前信道。一般情況下,對於客戶端來說,信道在下面兩種情況下狀態會變成Faulted:

調用超時,拋出TimeoutException

調用失敗,拋出CommunicationException

所以正確的客戶端進行服務調用的代碼應該如下面的代碼所示:通過try/catch控制服務調用,在try控制塊中進行正常服務調用並正常關閉服務代理進程(調用Close方法);在catch控制塊中,捕獲CommunicationException和TimeoutException這兩個異常,並將服務代理對象強行關閉(調用Abort方法)。下面的代碼演示了基於ChannelFactory<T>創建服務代理的WCF客戶端編程方式,對於直接通過強類型服務代理(繼承ClientBase<T>的服務代理類型)進行服務調用具有相同的結構。

   1: using (ChannelFactory<ICalculator> channelFactory = new ChannelFactory<ICalculator>("calculateservice"))
2: {
3: ICalculator calculator = channelFactory.CreateChannel();
4: try
5: {
6: int result = calculator.Divide(1, 0);
7: (calculator as ICommunicationObject).Close();
8: }
9: catch (CommunicationException ex)
10: {
11: //Exception Handling
12: (calculator as ICommunicationObject).Abort();
13: }
14: catch (TimeoutException ex)
15: {
16: //Exception Handling
17: (calculator as ICommunicationObject).Abort();
18: }
19: catch (Exception ex)
20: {
21: //Exception Handling
22: }
23: }

四、通過一些編程技巧避免重復代碼

如果嚴格按中上面的編程方式對CommunicationException和TimeoutException進出捕獲和處理,那麼你的客戶端代碼就會到處充斥中相同的代碼片斷。我不知一次說過,如果你的代碼中重復頻率過高,或者編程人員廣泛地采用Ctrl+C|Ctrl+V這樣的編程方式,那麼這就是你進行代碼重構的信號。

為此,我們可以通過對Delegate的利用來進行代碼的分離(服務調用代碼和異常處理代碼)。比如,我寫了下面兩個Invoke方法:

   1: using System;
2: using System.ServiceModel;
3: using Artech.ExceptionHandlingDemo.Contracts;
4: namespace Client
5: {
6: class Program
7: {
8: static void Invoke<TContract>(TContract proxy, Action<TContract> action)
9: {
10: try
11: {
12: action(proxy);
13: (proxy as ICommunicationObject).Close();
14: }
15: catch (CommunicationException)
16: {
17: (proxy as ICommunicationObject).Abort();
18: //Handle Exception
19: throw;
20: }
21: catch (TimeoutException )
22: {
23: (proxy as ICommunicationObject).Abort();
24: //Handle Exception
25: throw;
26: }
27: catch (Exception)
28: {
29: //Handle Exception
30: (proxy as ICommunicationObject).Close();
31: }
32: }
33:
34: static TReturn Invoke<TContract, TReturn>(TContract proxy, Func<TContract, TReturn> func)
35: {
36: TReturn returnValue = default(TReturn);
37: try
38: {
39: returnValue = func(proxy);
40: }
41: catch (CommunicationException)
42: {
43: (proxy as ICommunicationObject).Abort();
44: //Handle Exception
45: throw;
46: }
47: catch (TimeoutException)
48: {
49: (proxy as ICommunicationObject).Abort();
50: //Handle Exception
51: throw;
52: }
53: catch (Exception)
54: {
55: //Handle Exception
56: }
57:
58: return returnValue;
59: }
60: }
61: }

那麼,對CalculatorService就可以簡化成:

   1: using System;
2: using System.ServiceModel;
3: using Artech.ExceptionHandlingDemo.Contracts;
4: namespace Client
5: {
6: class Program
7: {
8: static void Main(string[] args)
9: {
10: using (ChannelFactory<ICalculator> channelFatory = new ChannelFactory<ICalculator>(new WSHttpBinding(), "http://127.0.0.1:3721/calculatorservice"))
11: {
12: ICalculator calcultor = channelFatory.CreateChannel(); int result = Invoke<ICalculator, int>(calcultor, proxy => proxy.Divide(2, 1)); //......
13: }
14: }
15: }
16: }
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved