阻止旧数据的BlockingCollection

时间:2014-02-20 11:01:54

标签: c# multithreading collections thread-safety producer-consumer

我有BlockingCollection。生产者任务向其添加项目,消费者任务删除项目。

现在我想限制集合中的项目数,如果添加了更多项目,则自动丢弃旧数据。该集合不应该同时包含超过N最近添加的项目。

因此,如果生产者添加新项目的速度快于消费者删除它们,我希望消费者只处理最新项目。

我可以在其构造函数中限制BlockingCollection的大小,但当然这只是意味着它在添加更多项时会阻塞,而不是它会删除旧项。

(我不想在生产者方面阻止,只有消费者方在从空集合中检索项目时才会阻止。)

我目前的解决方案是黑客攻击,只适用于1:的大小限制 (而且我不太确定它是否可靠。)

// My consumer task:
foreach (var item in blockingCollection.GetConsumingEnumerable())
{
    var lastItem = item;
    var lastItemTmp = item;
    while (blockingCollection.TryTake(out lastItemTmp))
           lastItem = lastItemTmp;
    // Now lastItem contains the most recent item in the collection, 
    // and older items have been discarded.
    // Proceed consuming lastItem ...
}

有更清洁的解决方案吗?

3 个答案:

答案 0 :(得分:7)

这样做:

void AddItemToQueue(MyClass item)
{
    while (!queue.TryAdd(item))
    {
        MyClass trash;
        queue.TryTake(out trash);
    }
}

如果在尝试添加项目时队列已满,则会从队列中删除项目。它使用TryTake因为可能(不太可能,但可能)某个其他线程可能已经从队列中删除了最后一个项目,然后才有机会获取它。

当然,假设您在构建BlockingCollection时指定了项目数限制。

另一种方法是,尽管它涉及的更多,但是创建自己的循环队列类,并让它实现IProducerConsumerCollection接口。然后,您可以使用该类的实例作为BlockingCollection的后备集合。实现循环队列并不是特别困难,尽管边缘情况很难实现。而且你必须使它成为一个并发数据结构,尽管使用锁定很容易。

如果您不希望队列经常溢出,或者队列的流量非常低(即每秒没有被击中数千次),那么我的初步建议将做你想要的,不会有性能问题。如果存在性能问题,则循环队列就是解决方案。

答案 1 :(得分:0)

我会使用Concurrent堆栈:

  

表示线程安全的后进先出(LIFO)集合。

     

http://msdn.microsoft.com/en-us/library/dd267331%28v=vs.110%29.aspx

我会在堆栈中发送一个包装你的任务的对象,为它添加一个时间戳。使用者将从堆栈中获取任务,并丢弃时间戳超过您定义的阈值的任务。

答案 2 :(得分:-1)

只需在将项目添加到此方法之前调用此方法。

public static void Clear<T>(this BlockingCollection<T> blockingCollection)
    {
        if (blockingCollection == null)
        {
            throw new ArgumentNullException("blockingCollection");
        }

        while (blockingCollection.Count > 0)
        {
            T item;
            blockingCollection.TryTake(out item);
        }
    }