在.NET Core中,ConcurrentQueue.Count非常缓慢

3
我正在使用一个ConcurrentQueue来从一个I/O绑定的任务中入队项目,并从另一个任务中出队以进行处理。当队列达到一定大小时,我停止向其添加项目,以便可以进行处理。为此,我检查了ConcurrentQueue.Count属性。
问题在于,Count属性似乎不像列表或其他集合中一样行为。它非常缓慢,而且队列越大,读取Count属性所需的时间就越长。在一个包含20k个项目的ConcurrentQueue中,几乎所有的处理器时间都花费在Count属性上。
粗略示例:
        while (reader.Read())
        {
            if(Queue.Count >= MaxQueueSize)
            {
                //Wait
            }
            //Do Stuff
        }

运行性能分析器时,所有时间都花费在System.Collections.Concurrent.CDSCollectionETWBCLProvicer.ctor()上。

这似乎只发生在.NET Core 2上,在.NET 4.6.2中不会出现此问题。

在.Net Core中有没有解决这个问题的方法?


嗨Evk,感谢提供信息。这在.NET Framework上不是问题。只有在.NET Core上存在问题。你确定获取Count需要访问所有项目吗? - Douglas Gaskell
我会查看BlockingCollection,谢谢!以FIFO方式获取项目时,这不会对性能产生严重影响吗? - Douglas Gaskell
更新:阻塞集合运作良好。如果您将其添加为答案,我会接受它。 - Douglas Gaskell
可能是线程安全有限大小队列的重复问题,或者是https://dev59.com/62jWa4cB1Zd3GeqPqWqO。 - mjwills
1个回答

5
自从这两个框架的源代码现在都可用,人们可以查看两个Count版本的源代码。对于完整的.NET版本,它在这里,而对于.NET Core版本,它在这里。你会发现:
  1. 两个版本都不是简单的。这不是像return _count这样的东西。

  2. .NET Core版本相当复杂,它包括缓慢的路径,在这些路径中,整个队列都会被冻结,以便计算稳定计数。

因此,.NET Core版本通常较慢并不足为奇,但主要问题是-它们都不是简单的,并且在紧密循环中使用它们不是一个好主意。除此之外-通过检查Count,你有一个竞争条件,因为它可能会在你检查它后立即被另一个线程更改,所以不能保证队列实际上包含小于你版本的最大计数。

相反,使用内置的容量有界集合,例如BlockingCollection

var x = new BlockingCollection<string>(new ConcurrentQueue<string>(), MaxQueueSize);

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