【问题标题】:IComparer is not used in LINQ expressionsIComparer 不在 LINQ 表达式中使用
【发布时间】:2020-04-19 10:09:11
【问题描述】:

我看到一些奇怪的行为,但无法弄清楚它是如何发生的。我有 2 个排序集,我使用缓冲区按序列号重新排序 TCP 数据包,并且我正在锁定语句中从这些集中读取数据包。

这是一个简化的例子:

SortedSet<Packet> in = new SortedSet<TcpPacket>(new SeqComparer()); // inbound buffer
SortedSet<Packet> out = new SortedSet<TcpPacket>(new SeqComparer()); // outbound buffer

public void BufferPacket(Packet packet) {
    // Some code which does not modify buffers
    bool buffer = isOutbound ? out : in;
    lock (buffer) {
        buffer.Add(packet);

        Packet bufferedPacket;
        // Removing retransmitted packets
        while (buffer.Count > 0 && (bufferedPacket = buffer.Min()).SeqNum < expected) {
            buffer.Remove(bufferedPacket);
        }

        // Process buffer
        while (buffer.Count > 0 && (bufferedPacket = buffer.Min()).SeqNum == expected) {
            buffer.Remove(bufferedPacket);
            expected += bufferedPacket.Length;
            ...
        }
    }
}

有些东西导致了这个异常:

System.ArgumentException: At least one object must implement IComparable.
   at System.Collections.Comparer.Compare(Object a, Object b)
   at System.Linq.Enumerable.Min[TSource](IEnumerable`1 source)

这似乎暗示在检查Count &gt; 0 之后,Min() 没有发现任何对象。我对锁定时如何发生这种情况感到非常困惑。

【问题讨论】:

  • lock 不会锁定集合/集合,它会阻止其他线程进入以下语句。如果你有其他地方可以修改buffer,它不使用相同的lock,仍然可以修改集合/集合。
  • 可以分享Packet的签名吗?它必须实现IComparable 才能使用Min
  • 对不起,我的示例实际上遗漏了我已将比较器传递给 SortedSet 以比较数据包的 seqnum。代码大部分时间运行良好,但随机遇到此故障。
  • Min 是一种 LINQ 方法,它不使用您传递给 SortedSetComparer。当集合中只有 0 到 1 个对象时,Min 不会比较对象。如果集合包含 2 个或更多项目,则 Min 比较 Packet 并且您将得到类似的异常。您能否验证当此代码有效时,集合中有多少项?
  • 因为集合是有序的,所以你不需要Min,你可以使用First()Last(),这取决于你的比较器如何比较。

标签: c# linq networking concurrency locking


【解决方案1】:

这是从问题下方的 cmets 中提取的答案

不是与锁相关的问题。

根据 cmets,OP 实现了 IComparer&lt;Packet&gt; 并将其传递给 SortedSet&lt;Packet&gt;。集合使用的比较器,Min 方法作为 LINQ 扩展不使用它。

相反,Min 期望 Packet 类实现 IComparable。并且因为类好像没有实现接口,所以抛出异常。

Min 在集合仅包含 0 或 1 个项目时不比较对象。当集合包含 2 个或更多项时,Min 比较 Packet 并抛出异常。

根据 OP 的评论,这就是混乱所在:

大多数时候缓冲区中只有 1 个数据包(除了极少数情况下我试图用这个缓冲区解决的乱序数据包。)

但是,在这种情况下,错误消息也可能具有误导性:

至少一个对象必须实现 IComparable。

这意味着集合中可能没有项目。

要解决此问题,因为集合已经订购,使用 FirstLast 按照 IComparer&lt;Packet&gt; 的实现应该可以工作。

【讨论】:

    【解决方案2】:

    不要使用 LINQ 库的 Enumerable.Min 扩展方法,而是使用 SortedSet.Min 属性:

    获取SortedSet&lt;T&gt; 中的最小值,由比较器定义。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-19
      • 2013-05-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-25
      • 1970-01-01
      相关资源
      最近更新 更多