不论是哪种操做,在任意时刻均可能出现不可预期的错误。问题在于咱们应该如何将错误报告给客户端。异常和异常处理机制是与特定技术紧密结合的,不能跨越边界的。此外,若是有客户端来处理错误,一定会致使耦合度增长。一般,错误处理应该是本地的实现细节,并不会影响到客户端。在设计良好的应用程序中,服务应该是被封装的,客户端没法知道有关错误的消息。设计良好的服务应尽量是自治的,不能依赖客户端去处理或恢复错误。任何非空的错误通知都应该是客户端与服务端之间契约交互的一部分。编程
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服务编程 第三版》