BlockingCollection<T> 抛出意外的 InvalidOperationException

问题描述 投票:0回答:2

我在.NET8中尝试

BlockingCollection<T>
(作为队列),有时我最终会遇到异常:

System.InvalidOperationException:集合参数为空,并且已被标记为添加完成。

代码示例:

    private static void TestMethod()
    {
        using BlockingCollection<DummyClass> queue = new BlockingCollection<DummyClass>();

        var task = Task.Run(() =>  //Produce
        {
            for (int i = 0; i < 10000000; i++)
            {
                queue.Add(new DummyClass());
            }
            queue.CompleteAdding();

        });

        int counter = 0;
        try
        {
            while (!queue.IsCompleted)  //Consume
            {
                DummyClass item = queue.Take(); // <-- Sometimes exception here
                counter++;
            }

            Console.WriteLine($"counter={counter} ");
        } 
        catch (Exception ex)
        {
            Console.WriteLine("Error:" + ex.ToString());
        }
    }

IsCompleted 状态“此集合是否已标记为完成添加且为空。”

因此,只要 CompleteAdding() 尚未被调用,Take() 就应该是阻塞的,并且当调用 CompleteAdding() 并且队列为空时,“ !queue.IsCompleted ”应该返回 false。

我错过了什么?

任何帮助将非常感激。

在 Windows 11 上的 VS2022 17.8.5 中运行。

c# .net producer-consumer .net-8.0 blockingcollection
2个回答
2
投票

你有一个线程竞赛;想象一下:

  • 队列为空
  • 线程 A 即将调用
    queue.CompleteAdding();
  • 线程 B 即将检查
     while (!queue.IsCompleted)
  • 线程B获得一些周期,发现它当前尚未完成
  • 线程 A 获得一些周期,将队列标记为完成
  • 线程 B 获得一些周期,调用
    Take()

和繁荣

解决方案:使用

TryTake


0
投票

食用

BlockingCollection<T>
的最佳方式是
GetConsumingEnumerable
方法:

foreach (DummyClass item in queue.GetConsumingEnumerable())
{
    // Process the consumed item
}

不幸的是,这种方便的方法在微软的文档和示例中并没有得到足够的重视。就我个人而言,我从来不需要该集合中的

IsCompleted
Take
成员。

© www.soinside.com 2019 - 2024. All rights reserved.