如何从任何两个BlockingCollections中获取优先于第一个集合的项目?

时间:2017-08-29 20:28:38

标签: c# producer-consumer

我有两个BlockingCollection<T>个对象,collection1collection2。我想使用这些集合中的项目优先考虑collection1中的项目。也就是说,如果两个集合都有项目,我想首先从collection1获取项目。如果他们都没有物品,我想等待物品可用。

我有以下代码:

public static T Take<T>(
    BlockingCollection<T> collection1,
    BlockingCollection<T> collection2) where T:class
{
    if (collection1.TryTake(out var item1))
    {
        return item1;
    }

    T item2;

    try
    {
        BlockingCollection<T>.TakeFromAny(
            new[] { collection1, collection2 },
            out item2);
    }
    catch (ArgumentException)
    {
        return null;
    }

    return item2;
}

在两个集合上调用null并且它们都为空时,此代码应返回CompleteAdding

此代码的主要问题是TakeFromAny方法的文档指定TakeFromAny如果在“集合”上调用ArgumentException,则会抛出CompleteAdding

  

的ArgumentException

     

collections参数是一个0长度的数组或包含一个null元素,或者已经在集合上调用了CompleteAdding()。

如果在任何集合中调用了CompleteAdding,它会抛出吗?或两个集合?

如果调用了CompleteAdding并且集合中仍有一些项目,它会抛出怎么办?

我需要一种可靠的方法来做到这一点。

在这段代码中,我试图首先从collection1获取,因为TakeFromAny的文档没有对收集顺序提供任何保证,如果两个集合都有项目,则从中获取项目。< / p>

这也意味着如果两个集合都是空的,然后它们会在以后同时收到项目,那么我可能会先从collection2获取一个项目,这很好。

修改

我将项目添加到两个集合(而不仅仅是单个集合)的原因是第一个集合没有上限,第二个集合没有上限。

有兴趣为什么我需要这些的人的更多细节:

我在一个名为ProceduralDataflow的开源项目中使用它。有关详细信息,请参阅此处https://github.com/ymassad/ProceduralDataflow

数据流系统中的每个处理节点都有两个集合,一个集合将包含第一次出现的项目(因此我需要在需要时减慢生产者的速度),另一个集合将包含第二个(或第三个)的项目,...)次(由于数据流中的循环)。

一个集合没有上限的原因是我不希望由于数据流中的循环而导致死锁。

1 个答案:

答案 0 :(得分:4)

首先,简要回答你的具体问题。

  

如果在任何集合中调用了CompleteAdding,它会抛出吗?或两个集合?

两者(全部) - 但仅限于任何集合中没有可用元素。

  

如果调用了CompleteAdding并且集合中仍有一些项目,它会抛出怎么办?

没有。如果集合中有可用元素,它将从集合中删除并返回给调用者。

<强>结论

显然文档不清楚。部分

  已在集合

上调用

CompleteAdding()

应该采用不同的方式 - 比如

  

任何集合中没有可用元素,并且已在所有集合上调用CompleteAdding()

<强>原理

嗯,我知道依赖于实施不是一个好习惯,但是当文档不清楚时,实现是我能想到的唯一可靠和官方的来源。因此,TakeFromAnyTryTakeFromAny都会reference source调用私有方法TryTakeFromAnyCore。它从以下开始:

ValidateCollectionsArray(collections, false);

false这是一个名为bool的{​​{1}}参数,在ValidateCollectionsArray内使用如下:

isAddOperation

这是为调用if (isAddOperation && collections[i].IsAddingCompleted) { throw new ArgumentException( SR.GetString(SR.BlockingCollection_CantAddAnyWhenCompleted), "collections"); } 的集合抛出ArgumentException的可能位置之一。正如我们所看到的,事实并非如此(问题#1)。

然后继续执行以下&#34;快速路径&#34;:

CompleteAdding()

这证明了问题#2的答案。

最后,如果任何集合中没有可用元素,则实现采用&#34;慢速路径&#34;通过调用另一个私有方法TryTakeFromAnyCoreSlow,以下注释是实现行为的基本解释:

//try the fast path first
for (int i = 0; i < collections.Length; i++)
{
    // Check if the collection is not completed, and potentially has at least one element by checking the semaphore count
    if (!collections[i].IsCompleted && collections[i].m_occupiedNodes.CurrentCount > 0 && collections[i].TryTake(out item))
        return i;
}

我们的问题的答案是#1和案例#5(注意单词 all )。顺便说一下,它还显示了//Loop until one of these conditions is met: // 1- The operation is succeeded // 2- The timeout expired for try* versions // 3- The external token is cancelled, throw // 4- The operation is TryTake and all collections are marked as completed, return false // 5- The operation is Take and all collection are marked as completed, throw TakeFromAny之间的唯一区别 - 案例#4与#5,即TryTakeFromAnythrow