同步请求
定义
定义
一、说明
有很多 小伙伴一直有一些需求:
- 客户端发送一个数据,然后等待服务器回应。
- 服务器向客户端发送一个数据,然后等待客户端回应。
那针对这些需求,可以使用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
时,将数据从插件再次传递,那么就需要在筛选函数中,自行触发插件。
例如:
var waitingClient = this.m_tcpClient.CreateWaitingClient(new WaitingOptions()
{
FilterFuncAsync = async (response) =>
{
var byteBlock = response.Memory;
var requestInfo = response.RequestInfo;
if (true)//如果满足某个条件,则响应WaitingClient
{
return true;
}
else
{
//否则
//数据不符合要求,waitingClient继续等待
//如果需要在插件中继续处理,在此处触发插件
await this.m_tcpClient.PluginManager.RaiseAsync(typeof(ITcpReceivedPlugin), this.m_tcpClient, new ReceivedDataEventArgs(byteBlock, requestInfo));
return false;
}
}
});
5.4 关于SendThenReturn与SendThenReturnAsync
在WaitingClient
中,所有的同步方法,其实都是异步转换来的,所以,功能一致。但是强烈建议如有可能,请务必使用异步发送来提高效率。
在主线程是GUI线程
(例如:winform
、wpf
等),如果在主线程中调用同步代码,也可能会导致死锁。所以请务必使用异步代码。
5.5 关于使用时机
WaitingClient
的机制是发送一个数据,然后等待响应,所以,使用时机,绝对不可以在Received
事件(或者插件)中调用。这将导致死锁。
例如:
var tcpClient = new TcpClient();
var tcpClient.Received =async (client,e) =>
{
var waitingClient = client.CreateWaitingClient(new WaitingOptions());
//这里将导致死锁
await waitingClient.SendThenResponseAsync("hello");
};
...
如果确实需要使用,请使用Task.Run
来异步处理。
例如:
this.m_tcpClient.Received =async (client,e) =>
{
//此处不能await,否则也会导致死锁
_ = Task.Run(async () =>
{
var waitingClient = client.CreateWaitingClient(new WaitingOptions());
await waitingClient.SendThenResponseAsync("hello");
});
};
...
5.6 其他
- 发送完数据,在等待时,如果收到其他返回数据,则可能得到错误结果。
- 发送采用同步锁,一个事务没结束,另一个请求也发不出去。
- waitClient的使用