WCF提供了单向操作,一旦客户端调用,WCF会生成一个请求,但没有相关的应答信息返回给客户端。所以,单向操作是不能有返回值,服务抛出的任何异常都不会传递给客户端。
理想情况下,一旦客户端调用了一个单向操作,它只会在要求调用的一瞬间被阻塞。事实上,单向调用不等于异步调用。当单向调用到达服务端时,不会立即分发这些调用,而是将调用方法服务端的队列中,并在某个时间分发。这一过程要根据服务配置的并发模式行为而定。服务要放入到队列中的消息个数与哦诶只的管道及可靠性模式有关。如果队列消息的数量超过了队列的容量,即使发出的只是单向调用,也会阻塞客户端。然而,一旦调用被放入队列中,就会取消对客户端的阻塞,继续执行。同时,服务会在后台处理这一操作。
开发人员容易错误的认为单向操作等于并发调用。如果客户端使用了相同的代理,但却利用了多线程调用单向操作,则在服务端的调用可能是并发,也可能不是。从本质上将,这种交互式是由服务并发管理模式和传输会话所决定的。
所有的WCF绑定都支持单向操作。
OperationContract特性定义了Boolean类型的IsOneWay属性:
public sealed class OperationContractAttribute : Attribute { public bool IsOneWay { get; set; } }IsOneWay属性的默认值为false,即默认操作为请求/应答操作,如果将IsOneWay属性设置为true,方法就会成为单向操作。
[ServiceContract] public interface IService7 { [OperationContract(IsOneWay=true)] void MyMethod(); }调用单向操作时,客户端并无任何特别之初。IsOneWay属性的值会包含在服务元数据中。注意,服务契约定义与客户端导入定义中的IsOneWay的值是相同的。
由于单向操作没有应答消息,因此它不能包含返回值。如下所示:
[ServiceContract] public interface IService7 { [OperationContract(IsOneWay=true)] void MyMethod(); }事实上,在加载宿主或打开代理时,WCF会强制要求验证方法的签名。
客户端不关心调用的结果,并不意味着它不关心调用是否发生。总而言之,即使采用了单向操作,也必须保证服务的可靠性,使他能够确保请求正确的传递到服务。
但客户端不会考虑单向操作的调用顺序,这也是WCF允许开发者将有效的可靠性传递从有效的有序传递与消息的执行中分离出来的主要原因。
WCF允许开发者设计一个具有单向操作的会话契约:
[ServiceContract(SessionMode=SessionMode.Required)] public interface IService7 { [OperationContract(IsOneWay=true)] void MyMethod(); }在这种配置下,如果客户端发出一个单向操作,则在执行方法时会关闭代理,然后阻塞客户端直到操作完成。
虽然技术上可行,但是在一个会话契约中包含一个单向操作是一种糟糕的设计。因为拥有一个会话往往意味着服务需要管理代表了客户端的状态。任何异常都可能破坏这个状态,而这时的客户端取无法获取异常。此外,客户端或服务之所以选择一个会话交互,是因为它使用的契约需要通过某个状态机 完成锁步执行。单调操作无法满足这种模式的要求。所以建议只能将单向操作应用到单向服务或者单例服务中。
如果在会话契约中定义了单向操作,就必须保证单向操作是终止会话的最后一个操作。这个可以通过分布操作来实现:
[ServiceContract(SessionMode=SessionMode.Required)] public interface IService7 { [OperationContract()] void MyMethod(); [OperationContract(IsOneWay = true,IsInitiating=false,IsTerminating=true)] void Over(); }
即使单向操作没有返回值,也不会从服务端返回异常,但客户端仍然需要获得单向调用的异常,设置推断调用在服务段已经失败。
如果没有传输会话(使用BasicHttpBingding绑定或不包含可靠消息传输与安全的WsHttpBingding绑定),在调用一个单向操作期间,如果发生异常,客户端并不会受到影响,会继续发出对相同代理实例的调用:
[ServiceContract] public interface IService7 { [OperationContract(IsOneWay =true)] void MethodWithError(); [OperationContract] void MethodWithoutError(); } [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall,ConcurrencyMode=ConcurrencyMode.Single)] public class Service7:IService7 { public void MethodWithError() { throw new Exception(); } public void MethodWithoutError() { } } //不具有传输会话 MyContractClient proxy = new MyContractClient(); proxy.MethodWithError(); proxy.MethodWithoutError(); proxy.Close();如果存在传输会话,那么一个服务端的异常(包含单向操作抛出的异常)就会操作通道错误。此时,客户端不能发出任何一个使用相同代理实例的新的调用。
[ServiceContract] public interface IService7 { [OperationContract(IsOneWay =true)] void MethodWithError(); [OperationContract] void MethodWithoutError(); } [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerCall,ConcurrencyMode=ConcurrencyMode.Single)] public class Service7:IService7 { public void MethodWithError() { throw new Exception(); } public void MethodWithoutError() { } } //具有传输会话 MyContractClient proxy = new MyContractClient(); proxy.MethodWithError(); try { proxy.MethodWithoutError();//因为通道错误,从而会被抛出 proxy.Close(); } catch {}客户端甚至不能安全地关闭代理。因此,单向操作并不具备即发即弃的特性,因为客户端在调用期间会发现服务端出现的错误。
以后会展示如何真正的异步即发即弃的操作使用单向操作。