WCF客户端导致服务器挂起,直到连接错误

Mr.*_*ves 6 concurrency performance wcf asynchronous fire-and-forget

以下文字旨在扩展并为此问题添加颜色:

如何防止行为不端的客户端取消整个服务?

我基本上有这样的场景:一个WCF服务启动并运行一个客户端回调,具有直接,简单的单向通信,与此没有很大不同:

public interface IMyClientContract
{
  [OperationContract(IsOneWay = true)]
  void SomethingChanged(simpleObject myObj);
}
Run Code Online (Sandbox Code Playgroud)

我这个方法每秒可能会调用这种方法几千次,最终会有50个并发连接的客户端,延迟时间尽可能低(<15 ms会很好).这工作正常,直到我在连接到服务器的其中一个客户端应用程序上设置了一个断点,然后一切都挂起,可能2-5秒后服务挂起,其他任何客户端都没有收到任何数据大约30秒左右,直到服务注册连接故障事件并断开违规客户端.在此之后,所有其他客户端继续以愉快的方式接收消息.

我已经研究过serviceThrottling,并发性调整,设置线程池最小线程,WCF秘密酱和整个9码,但最后这篇文章MSDN - WCF要点,单向呼叫,回调和事件 完全描述了问题我没有真正提出建议.

允许服务安全地回调客户端的第三种解决方案是将回调契约操作配置为单向操作.即使并发设置为单线程,这样做也可以使服务回调,因为没有任何回复消息可以争用锁.

但是在文章的早些时候,它只是从客户的角度描述了我所看到的问题

当单向呼叫到达服务时,它们可能不会一次全部调度,并且可能在服务端排队等待一次一个地调度,所有这些都根据服务配置的并发模式行为和会话模式.服务愿意排队的消息(无论是单向消息还是请求 - 答复)是配置的信道和可靠性模式的产物.如果排队消息的数量超过了队列的容量,则客户端将阻止,即使在发出单向呼叫时也是如此

我只能假设反之亦然,到客户端的排队消息数超过了队列容量,并且线程池现在充满了试图调用此客户端的线程,这些线程现在全部被阻止.

处理这个问题的正确方法是什么?我是否应该研究一种方法来检查每个客户端在服务通信层排队的消息数量,并在达到某个限制后中止其连接?

几乎看来,如果WCF服务本身在队列填满时阻塞,那么每当一个客户端的队列满了时,我在服务中实现的所有异步/单向/即发即弃策略仍将被阻止.

Mr.*_*ves 1

更新:

我实现了“即发即忘”设置来调用客户端的回调通道,一旦缓冲区填满客户端,服务器就不再阻塞

MyEvent 是一个带有委托的事件,该委托与 WCF 客户端合约中定义的方法之一相匹配,当它们连接时,我实质上是将回调添加到事件中

MyEvent += OperationContext.Current.GetCallbackChannel<IFancyClientContract>().SomethingChanged
Run Code Online (Sandbox Code Playgroud)

等等...然后将此数据发送给所有客户端,我正在执行以下操作

//serialize using protobuff
using (var ms = new MemoryStream())
{
    ProtoBuf.Serializer.Serialize(ms, new SpecialDataTransferObject(inputData));
    byte[] data = ms.GetBuffer();
    Parallel.ForEach(MyEvent.GetInvocationList(), p => ThreadUtil.FireAndForget(p, data));
}
Run Code Online (Sandbox Code Playgroud)

在 ThreadUtil 类中,我对即发即弃文章中定义的代码进行了以下更改

static void InvokeWrappedDelegate(Delegate d, object[] args)
{
    try
    {
        d.DynamicInvoke(args);
    }
    catch (Exception ex)
    {
        //THIS will eventually throw once the client's WCF callback channel has filled up and timed out, and it will throw once for every single time you ever tried sending them a payload, so do some smarter logging here!!
        Console.WriteLine("Error calling client, attempting to disconnect.");
        try
        {
            MyService.SingletonServiceController.TerminateClientChannelByHashcode(d.Target.GetHashCode());//this is an IContextChannel object, kept in a dictionary of active connections, cross referenced by hashcode just for this exact occasion
        }
        catch (Exception ex2)
        {
            Console.WriteLine("Attempt to disconnect client failed: " + ex2.ToString());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我没有任何好主意如何去杀死服务器仍在等待的所有待处理数据包,看看它们是否会被传递。一旦我得到第一个异常,理论上我应该能够去并终止某个队列中的所有其他请求,但这个设置是有效的并且满足目标。