我有多个生产者和一个消费者。但是,如果队列中存在尚未消耗的内容,则生产者不应再次对其进行排队。 (唯一没有重复的阻塞使用默认并发队列的集合)
if (!myBlockingColl.Contains(item))
myBlockingColl.Add(item)
然而,blocking coll没有contains方法,也没有提供任何类型的trypeek()方法。如何访问底层并发队列,以便我可以执行类似
的操作if (!myBlockingColl.myConcurQ.trypeek(item)
myBlockingColl.Add(item)
尾巴旋转。请帮忙。感谢
答案 0 :(得分:8)
这是一个有趣的问题。这是我第一次看到有人要求阻止重复的阻塞队列。奇怪的是,我发现BCL中已经存在的东西并不像你想要的那样。我说这很奇怪,因为BlockingCollection
可以接受IProducerConsumerCollection
作为基础集合,其中TryAdd
方法被宣告为在检测到重复项时能够失败。问题是我没有看到IProducerConsumerCollection
的具体实现可以防止重复。至少我们可以写自己的。
public class NoDuplicatesConcurrentQueue<T> : IProducerConsumerCollection<T>
{
// TODO: You will need to fully implement IProducerConsumerCollection.
private Queue<T> queue = new Queue<T>();
public bool TryAdd(T item)
{
lock (queue)
{
if (!queue.Contains(item))
{
queue.Enqueue(item);
return true;
}
return false;
}
}
public bool TryTake(out T item)
{
lock (queue)
{
item = null;
if (queue.Count > 0)
{
item = queue.Dequeue();
}
return item != null;
}
}
}
既然我们的IProducerConsumerCollection
不接受重复,我们可以像这样使用它:
public class Example
{
private BlockingCollection<object> queue = new BlockingCollection<object>(new NoDuplicatesConcurrentQueue<object>());
public Example()
{
new Thread(Consume).Start();
}
public void Produce(object item)
{
bool unique = queue.TryAdd(item);
}
private void Consume()
{
while (true)
{
object item = queue.Take();
}
}
}
您可能不喜欢我NoDuplicatesConcurrentQueue
的实施。如果您认为需要TPL集合提供的低锁性能,您当然可以使用ConcurrentQueue
或其他任何方式自行实现。
<强>更新强>
今天早上我能够测试代码。有一些好消息和坏消息。好消息是,这将在技术上有效。坏消息是您可能不希望这样做,因为BlockingCollection.TryAdd
拦截了基础IProducerConsumerCollection.TryAdd
方法的返回值,并在检测到false
时抛出异常。是的,这是对的。它不像您期望的那样返回false
,而是生成异常。我必须说实话,这既令人惊讶又荒谬。 TryXXX方法的重点是它们不应该抛出异常。我非常失望。
答案 1 :(得分:5)
除了Brian Gideon在 Update 之后提到的警告之外,他的解决方案还存在这些性能问题:
queue.Contains(item)
)上的以下代码通过
改进了Brian的解决方案System.Collections.Concurrent
命名空间 N.B。由于没有ConcurrentHashSet
,我使用ConcurrentDictionary
,忽略了这些值。
在极少数情况下,幸运的是,可以简单地从多个更简单的并发数据结构中构建更复杂的并发数据结构,而无需添加锁。这两个并发数据结构的操作顺序非常重要。
public class NoDuplicatesConcurrentQueue<T> : IProducerConsumerCollection<T>
{
private readonly ConcurrentDictionary<T, bool> existingElements = new ConcurrentDictionary<T, bool>();
private readonly ConcurrentQueue<T> queue = new ConcurrentQueue<T>();
public bool TryAdd(T item)
{
if (existingElements.TryAdd(item, false))
{
queue.Enqueue(item);
return true;
}
return false;
}
public bool TryTake(out T item)
{
if (queue.TryDequeue(out item))
{
bool _;
existingElements.TryRemove(item, out _);
return true;
}
return false;
}
...
}
N.B。查看此问题的另一种方法:您希望一个保留插入顺序的集合。
答案 2 :(得分:1)
我建议用锁来实现你的操作,这样你就不会以破坏它的方式读取和写入项目,使它们成为原子。例如,使用任何IEnumerable:
object bcLocker = new object();
// ...
lock (bcLocker)
{
bool foundTheItem = false;
foreach (someClass nextItem in myBlockingColl)
{
if (nextItem.Equals(item))
{
foundTheItem = true;
break;
}
}
if (foundTheItem == false)
{
// Add here
}
}