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

时间:2011-07-11 15:43:13

标签: wcf asynchronous concurrency performance fire-and-forget

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

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

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

public interface IMyClientContract
{
  [OperationContract(IsOneWay = true)]
  void SomethingChanged(simpleObject myObj);
}

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

我已经研究过serviceThrottling,并发性调整,设置线程池最小线程,WCF秘密酱和整个9码,但在一天结束时,本文MSDN - WCF essentials, One-Way Calls, Callbacks and Events正好描述了我遇到的问题没有真正的推荐。

  

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

但在文章的前面部分描述了我所看到的问题,仅从客户的角度来看

  

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

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

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

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

2 个答案:

答案 0 :(得分:1)

对客户端回调知之甚少,但听起来与通用wcf代码阻塞问题类似。我经常通过生成BackgroundWorker并在线程中执行客户端调用来解决这些问题。在此期间,主线程计算子线程占用的时间。如果孩子还没有在几毫秒内完成,主线程就会继续前进并放弃线程(它最终会自行死亡,因此没有内存泄漏)。这基本上是格拉夫斯先生用“火与忘记”这句话所暗示的。

答案 1 :(得分:1)

<强>更新

我实现了Fire-and-forget设置来调用客户端的回调通道,一旦缓冲区填充到客户端,服务器就不再阻塞

MyEvent是一个事件,其代理与WCF客户端契约中定义的方法之一匹配,当它们连接时,我实际上是将回调添加到事件

MyEvent += OperationContext.Current.GetCallbackChannel<IFancyClientContract>().SomethingChanged

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

//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));
}
在ThreadUtil类中的

我基本上对fire-and-foget文章中定义的代码进行了以下更改

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());
        }
    }
}

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