WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理

在前面一片文章(服务代理不能得到及时关闭会有什么后果?)中,我们谈到及时关闭服务代理(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: }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏大内老A

WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇]

在进行基于会话信道的WCF服务调用中,由于受到并发信道数量的限制,我们需要及时的关闭信道;当遇到某些异常,我们需要强行中止(Abort)信道,相关的原理,可以参...

32510
来自专栏黑泽君的专栏

异常的分类图解

    Error:走到半路上,发生山路塌陷,或者出现了泥石流,这个问题很严重,不是班长能够立马解决的。

1231
来自专栏Java编程技术

JDK8中新增原子性操作类LongAdder

LongAdder类似于AtomicLong是原子性递增或者递减类,AtomicLong已经通过CAS提供了非阻塞的原子性操作,相比使用阻塞算法的同步器来说性能...

841
来自专栏小狼的世界

几个有用的Excel VBA脚本

最近有个朋友要处理很多的Excel数据,但是手工处理又太慢,让我帮忙处理。通过搜索和自己的编写,帮他写了几个脚本,大大提高了工作效率。其实Excel中的脚本(宏...

1462
来自专栏武培轩的专栏

搜狗面经汇总

volatile是变量修饰符,其修饰的变量具有可见性,Java的做法是将该变量的操作放在寄存器或者CPU缓存上进行,之后才会同步到主存,使用volatile修饰...

2345
来自专栏大内老A

这算是ASP.NET MVC的一个大BUG吗?

这是昨天一个同事遇到的问题,我觉得这是一个蛮大的问题,而且不像是ASP.NET MVC的设计者有意为之,换言之,这可能是ASP.NET MVC的一个Bug(不过...

2178
来自专栏Java3y

还在用Synchronized?Atomic你了解不?

之前在学习的时候也看过AtomicInteger类很多次了,一直没有去做相关的笔记。现在遇到问题了,于是就过来写写笔记,并希望在学习的过程中解决掉问题。

1171
来自专栏芋道源码1024

【死磕Java并发】—- 深入分析CAS

CAS,Compare And Swap,即比较并交换。Doug lea大神在同步组件中大量使用CAS技术鬼斧神工地实现了Java多线程的并发操作。整个AQS同...

38811
来自专栏jeremy的技术点滴

解决dubbo导致tomcat无法优雅shutdown的问题

5724
来自专栏chenssy

【死磕Java并发】—-深入分析CAS

CAS,Compare And Swap,即比较并交换。Doug lea大神在同步组件中大量使用CAS技术鬼斧神工地实现了Java多线程的并发操作。整个AQS同...

3335

扫码关注云+社区

领取腾讯云代金券