【问题标题】:WCF client causes server to hang until connection faultWCF 客户端导致服务器挂起,直到连接错误
【发布时间】:2023-03-19 08:20:01
【问题描述】:

下面的文字是对这个问题的扩展和添加颜色:

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

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

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

我可能每秒从服务调用此方法数千次,最终将有大约 50 个并发连接的客户端,并且延迟尽可能低(一个 客户端应用程序上设置断点,然后在服务挂起 2-5 秒后一切都挂起,并且其他客户端都没有收到任何数据约大约 30 秒,直到服务注册连接故障事件并断开有问题的客户端。此后,所有其他客户端继续愉快地接收消息。

我已经对 serviceThrottling、并发调整、设置线程池最小线程、WCF 秘诀和整个 9 码进行了研究,但归根结底,这篇文章 MSDN - WCF essentials, One-Way Calls, Callbacks and Events 准确描述了我遇到的问题提出建议。

允许服务安全回调客户端的第三种解决方案是将回调合约操作配置为单向操作。这样做可以让服务在并发设置为单线程的情况下进行回调,因为不会有任何回复消息来争夺锁。

但在本文前面,它仅从客户的角度描述了我所看到的问题

当单向调用到达服务时,它们可能不会一次全部调度,可能会在服务端排队等待一次调度,这一切都取决于服务配置的并发模式行为和会话模式。服务愿意排队多少条消息(无论是单向还是请求-回复)是配置的通道和可靠性模式的产物。如果排队的消息数量超过了队列的容量,那么客户端将阻塞,即使发出单向调用也是如此

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

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

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

【问题讨论】:

  • 带详细信息的长格式表示赞赏,但执行摘要 / TL;DR 也将不胜感激。
  • 您是否检查了性能计数器或跟踪的网络活动以查看在您暂停的客户端上阻止了多少请求?
  • 我正在尝试fire-and-forget,这实际上显示了一些进展。我用 catch 块包装同步委托 DynamicInvoke,然后使用 delegate.Target.GetHashCode() 作为当前连接字典的键来识别有问题的客户端通道,使用它来终止客户端连接......不要知道这是否可扩展
  • @Ladislav Mrnka,这是个好主意,如果你知道有一个好方法来计算有多少邮件被阻止,我会这样做

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


【解决方案1】:

不太了解客户端回调,但听起来类似于通用的 wcf 代码阻塞问题。我经常通过生成 BackgroundWorker 并在线程中执行客户端调用来解决这些问题。在此期间,主线程计算子线程花费的时间。如果子线程在几毫秒内还没有完成,则主线程继续前进并放弃线程(它最终会自行死亡,因此不会发生内存泄漏)。这基本上就是 Mr.Graves 用“即发即弃”这一短语所暗示的。

【讨论】:

  • +1!我想知道在玩发布订阅时如何解决同样的问题。如果一个客户无限期地阻塞而不死,那它就是在扼杀我的服务。没想到这样做:)
【解决方案2】:

更新:

我实现了一个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 类中,我基本上对文章中定义的代码进行了以下更改

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 是否有什么收获
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多