C#如何更新BlockingCollection中的元素<t>?</t>

时间:2013-12-18 12:30:14

标签: c# concurrency

我有一个ConcurrentDictionaryWrapper类包装ConcurrentDictionary<K,V>并实现IProducerConsumerCollection接口,因此我可以将其与BlockingCollection一起使用。生产者将使用密钥向BlockingCollection添加值。我们的想法是,如果密钥存在,我们将替换基础字典中的值。我的ConcurrentDictionaryWrapper.TryAdd()方法如下:

public bool TryAdd(KeyValuePair<TKey, TValue> item)
{
    _wrapped[item.Key] = item.Value
    return true;
}

我看到的问题是,如果替换了值,BlockingCollection会将此视为一个补充。

var wrapper = new ConcurrentDictionaryWrapper<string, object>();
var bc = new BlockingCollection<KeyValuePair<string, object>>(wrapper);
bc.TryAdd(new KeyValuePair<string, object>("key", new object()));
bc.TryAdd(new KeyValuePair<string, object>("key", new object()));
wrapper.Count; # returns 1
bc.Count; # returns 2

我无法在上面的TryAdd()中返回false,因为BlockingCollection会引发InvalidOperationException。

有没有办法实现我想要的行为?我希望这一点尽可能简单,但似乎没有办法通过实现IProducerConsumerCollection来使用BlockingCollection进行这种“AddOrUpdate”行为。我想在调用BlockingCollection上的TryTake()之前避免使用TryAdd() - 我可能只是使用围绕Dictionary的标准锁并使生成器/使用者与AutoResetEvent同步。

2 个答案:

答案 0 :(得分:1)

也许,而不是替换,将之前的项目标记为无效可能更适合。为每个值保留一个布尔标志。添加具有现有密钥的项目时,找到上一个项目,将其标记设置为false并添加标记设置为true的新项目。

然后,当消费者获得该项目时,它将检查它是否有效。如果没有,它会扔掉它并请求新的。

答案 1 :(得分:1)

这里存在相当大的摩擦,BlockingCollection&lt;&gt;维护自己的计数,不使用您的IProducerConsumerCollection.Count实现。因此,由于您没有使TryAdd()失败,因此BlockingCollection现在确信集合中有两个项。即使你只能检索其中一个。所以你肯定不能保持原样,你的代码总是会死锁。

您需要的是一个BlockingCollection方法,如果该项已经存在,则返回 false 或无提示失败。但它没有,InvalidOperationException是不可避免的。你可以抓住它,但这很难看。

替代方法是一个Contains()方法,如果它返回 true ,则用它来避免调用TryAdd()。但现在该集合不再是线程安全的。这就是BlockingCollection没有那种方法的原因。

这是一块岩石和一块坚硬的地方,你无法让它像预想的那样工作。你必须自己旋转。最好窃取线程主机的代码,this magazine article中的有界缓冲区应该是使锁设计正确的良好开端。