WCF客户端导致服务器挂起直到连接故障

6
以下文字是为了扩展和丰富这个问题而写的:
如何防止一个不良客户端使整个服务崩溃?
我的情况基本上是这样的:一个WCF服务正在运行,并且具有客户端回调,其单向通信非常简单,与此类似:
public interface IMyClientContract
{
  [OperationContract(IsOneWay = true)]
  void SomethingChanged(simpleObject myObj);
}

我正在从服务端向大约50个并发连接的客户端中的一个潜在地以每秒数千次的速度调用此方法,并希望延迟尽可能低(<15毫秒最好)。这样做一切正常,直到我在连接到服务器的某个客户端应用程序上设置断点,然后所有东西都停止工作,在大约2-5秒钟后服务挂起,并且其他所有客户端在大约30秒左右没有收到任何数据,直到服务注册了连接故障事件并断开有问题的客户端。之后,所有其他客户端继续接收消息。

我已经研究了serviceThrottling、并发优化、设置线程池最小线程、WCF秘密酱和整个过程,但归根结底,这篇文章MSDN-WCF essentials,单向调用,回调和事件描述了我正在遇到的问题,但并没有真正提出建议。

允许回调契约操作配置为单向操作的第三个解决方案可以使服务安全地回调客户端。这样做可以使服务在并发设置为单线程时进行回调,因为不会有任何回复消息争夺锁。

但是在文章的前面,它描述了我所看到的问题,只是从客户端的角度描述。

当单向调用到达服务时,它们可能不会一次性分派并可能在服务端排队以一个接一个地分派,这完全取决于服务配置的并发模式行为和会话模式。服务愿意排队多少消息(无论是单向还是请求-回复)是由配置的通道和可靠性模式确定的。如果排队的消息数超过了队列的容量,那么即使发出单向调用,客户端也会被阻塞。

我只能假设相反的情况是真的,即客户端的排队消息数已超过队列容量,线程池现在被尝试调用该客户端的线程填满,现在所有线程都被阻塞。

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

似乎如果WCF服务本身在队列填充时被阻塞,则我在服务内部实施的所有异步/单向/快速执行策略仍将被阻塞,每当一个客户端的队列被填满时。


长篇详细解释会更好,但是也可以提供执行摘要或简短的总结。 - Brian Driscoll
你是否检查了性能计数器或跟踪网络活动,以查看在暂停的客户端上有多少请求被阻止了? - Ladislav Mrnka
1
我正在尝试使用“fire-and-forget”(http://www.yoda.arachsys.com/csharp/threads/threadpool.shtml),并且实际上已经取得了一些进展。我正在用一个catch块包装同步委托DynamicInvoke,然后使用delegate.Target.GetHashCode()作为当前连接字典中标识有问题的客户端通道的键,以此来终止客户端连接……不知道这是否可扩展。 - Mr. Graves
@Ladislav Mrnka,这是个好主意,如果你知道一个好的方法来计算有多少消息被阻止,我会完全支持。 - Mr. Graves
2个回答

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-forget”文章中定义的代码进行了以下更改。
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());
        }
    }
}

我没有任何好的想法来处理服务器仍在等待交付的所有挂起数据包。一旦我得到第一个异常,理论上我应该能够去终止某个队列中的所有其他请求,但这个设置是功能性的并且达到了目标。


我不知道在这里使用Parallel.ForEach是否会带来任何好处。 - Mr. Graves

1

关于客户端回调我不是很了解,但听起来与通用的WCF代码阻塞问题相似。我经常通过生成BackgroundWorker并在线程中执行客户端调用来解决这些问题。在此期间,主线程计算子线程所需时间。如果子线程在几毫秒内没有完成,主线程就会继续执行并放弃该线程(它最终会自行死亡,因此不会有内存泄漏)。这基本上就是Graves先生所建议的“fire-and-forget”。


+1!我曾经在使用发布订阅时遇到同样的问题,不知道该如何解决。如果客户端无限期地阻塞而不死亡,它会导致我的服务崩溃。没想到可以这样做 :) - Franchesca

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接