同步请求
定义
一、说明
有很多小伙伴一直有一些需求:
- 客户端发送一个数据,然后等待服务器回应。
- 服务器向客户端发送一个数据,然后等待客户端回应。
那针对这些需求,可以使用WaitingClient其内部实现了IWaitSender接口,能够在发送完成后,直接等待返回。
WaitingClient是一种发送-响应机制,其原理是IReceiverClient,只要实现该接口的组件均可以使用。
例如:TcpClient、TcpService、NamedPipeClient、NamedPipeService、SerialPortClient等。
二、在客户端使用
在客户端工作时,支持很多组件,例如:TcpClient、NamedPipeClient、SerialPortClient,下面仅以TcpClient为例。
三、在服务器使用
同理,在客户端工作时,支持很多组件,例如:TcpService、NamedPipeService,下面仅以TcpService为例。
WaitingClient在创建以后,可以长久使用,直到原始的组件被Dispose释放。所以即使是断线重连后,也会是有效的。所以没必要在使用时每次都创建。
四、配置
4.1 超时配置
在默认情况下,超时时间是5秒。
但是有时候,我们希望不设置超时时间,而是由用户自己控制超时时间,也就是能有取消的等待。
4.2 筛选配置
筛选函数,用于筛选符合要求的数据。因为WaitingClient的响应机制是建立在一问一答的基础之上实现的,所以,在发送完数据后,可能收到之前的过期响应数据,那么这时候,会根据用户设置的筛选函数,判断是否响应。
在默认情况下,筛选函数为空,即不筛选。
所以可以设置筛选函数。
如果需要异步判断,则可以设置异步筛选函数,可以返回一个FilterFuncAsync,用于异步筛选。
筛选函数的参数是Response,它包含两个属性,分别为RequestInfo和Memory,具体使用哪个属性,看适配器类型
五、使用注意事项
5.1 线程安全
WaitingClient是线程安全的,可以多线程使用。但是实际上内部有SemaphoreSlim锁,所以,如果使用在多线程中,可能会造成大量锁竞争,从而降低效率。
5.2 关于ReadAsync
WaitingClient的原理实际上是使用了IReceiverClient接口,这也就意味着它不能和ReadAsync同时使用。
5.3 关于筛选函数
在筛选函数FilterFunc(Async)中,不管返回true还是false,数据都不再向下传递。这也就意味着一旦使用WaitingClient,在其等待返回的时段中,即使收到了无效数据,也不会再触发Receive事件(或者相关插件)。
如果不在等待时段,则不受影响。
如果想要实现,当筛选函数返回false时,将数据从插件再次传递,那么就需要在筛选函数中,自行触发插件。
5.4 关于SendThenReturn与SendThenReturnAsync
在WaitingClient中,所有的同步方法,其实都是异步转换来的,所以,功能一致。但是强烈建议如有可能,请务必使用异步发送来提高效率。
在主线程是GUI线程(例如:winform、wpf等),如果在主线程中调用同步代码,也可能会导致死锁。所以请务必使用异步代码。
5.5 关于使用时机
WaitingClient的机制是发送一个数据,然后等待响应,所以,使用时机,绝对不可以在Received事件(或者插件)中调用。这将导致死锁。
如果确实需要使用,请使用Task.Run来异步处理。
5.6 其他
- 发送完数据,在等待时,如果收到其他返回数据,则可能得到错误结果。
- 发送采用同步锁,一个事务没结束,另一个请求也发不出去。
- waitClient的使用不可以直接在
Received相关触发中使用,因为必然会导致死锁,详见:#I9GCGT 。 - 在Net461及以下版本中,SendThenReturn与SendThenReturnAsync不能混合使用。即:要么全同步,要么全异步(这可能是.net bug)。
六、高级实现方式
使用WaitingClient的方式虽然简单通用,但是其实质并不建议在要求较高的环境中使用,尤其是对“请求-响应”要求较高的时候。
一般来说,使用WaitingClient的不可靠性来源于2个方面:
- 响应机制是依赖
Reveiver来实现的,会在请求时创建Reveiver,在响应时,会销毁Reveiver。这其中可能导致数据丢失。 - 响应数据没办法和请求进行关联,导致数据可能被其他数据打断。
那么基于此,如果您有更高要求的需求,我们建议使用以下方法实现:
6.1 设计通信协议,以支持响应数据关联
- 在请求时,请求数据中添加一个
requestId,用于标识请求。 - 在响应时,响应数据中添加一个
requestId,用于标识响应。 - 在响应时,将响应数据发送给对应的请求方。
- 在请求方收到响应数据时,将响应数据与对应的请求进行关联。
- 在响应方收到请求数据时,将请求数据发送给对应的响应方。
- 在响应方收到响应数据时,将响应数据发送给对应的请求方。