BlockingCollection中的元素顺序<>

时间:2011-09-21 15:39:35

标签: .net multithreading collections concurrency queue

我有一个使用BlockingCollection<>实施的下载队列。现在我想暂时优先考虑一些下载。我认为将一些元素“向上”移动可能会很棒,就像在列表中一样,但是没有像Remove()/ AddFirst()或Move()这样的方法。

BlockingCollection<>中安排项目的首选方法是什么?

4 个答案:

答案 0 :(得分:17)

不幸的是,没有办法以你想要的方式重新排列队列。你真正需要的是PriorityBlockingCollection作为优先级队列实现,但是也不存在。

您可以利用TakeFromAny方法获取所需的优先级行为。 TakeFromAny将从BlockingCollection个实例数组中取消第一个可用项。它将优先考虑数组中第一个列出的队列。

var low = new BlockingCollection<object> { "low1", "low2" };
var high = new BlockingCollection<object> { "high1", "high2" };
var array = new BlockingCollection<object>[] { high, low };
while (true)
{
  object item;
  int index = BlockingCollection<object>.TakeFromAny(array, out item);
  Console.WriteLine(item);
}

上面的示例将打印:

high1
high2
low1
low2

它强制您使用多个队列,因此它不是最优雅的解决方案。

答案 1 :(得分:8)

BlockingCollection<T>通过包装内部IProducerConsumerCollection<T>来发挥作用。默认设置是在内部使用ConcurrentQueue<T>,但您可以通过this constructor提供自己的实施。

如果您提供自己的线程安全集合,则可以使用所需的任何集合类型。这将允许您根据需要确定元素的优先级。

虽然没有可实现所需功能的内置集合,但您可以将一对ConcurrentQueue<T>集合包装到实现IProducerConsumerCollection<T>的类中。这将允许您拥有“高优先级”和“低优先级”元素。

答案 2 :(得分:5)

无法直接在BlockingCollection<T>之上实现优先级队列。 BlockingCollection<T>最好被视为严格的队列,不能实现重新排序。

但是,您可以使用优先级队列和BlockingCollection<T>的组合来实现相同的效果。让我们假设你实现了一个简单的PriorityQueue<T>,它正确地命令你的下载。以下内容可用于为接收方的处理添加优先级

class DownloadManager {
  private PriorityQueue<Download> m_priorityQueue;
  private BlockingCollection<Download> m_downloadCollection;

  public bool TryGetNext(ref Download download) {
    PumpDownloadCollection();
    if (m_priorityQueue.IsEmpty) {
      download = null;
      return false;
    }

    download = m_priorityQueue.Dequeue();
    return true;
  }

  private void PumpDownloadCollection() {
    T value;
    while (m_downloadCollection.TryTake(out value)) {
      m_priorityQueue.Enqueue(value);
    }
  }

注意:PriorityQueue<T>不是.Net Framework中实际存在的类型。根据下载项目的优先级安排,您需要自己编写。

答案 3 :(得分:3)

Reed告诉您需要实施IProducerConsumerCollection<T>是正确的。但是,有一个课程可以帮助你。它不是内置的,但它在MSDN上有特色。只需将此ConcurrentPriorityQueue传递给您的BlockingCollection

这就是我使用它的方式:

private readonly BlockingCollection<KeyValuePair<int, ICommand>> _commands 
    = new BlockingCollection<KeyValuePair<int, ICommand>>(
        new ConcurrentPriorityQueue<int, ICommand>());

ICommand是我项目中的一个界面。

现在,您可以添加以下项目:

_actions.Add(new KeyValuePair<int, ICommand>(1, command1));
_actions.Add(new KeyValuePair<int, ICommand>(2, command2));
_actions.Add(new KeyValuePair<int, ICommand>(1, command3));

首先执行具有较低整数值作为优先级的项目。在上面的例子中:

command1
command3
command2

在您BlockingCollection上方循环时,您将不再获得单个元素(在我的情况下为ICommand),而是KeyValuePair。当然,这可能需要进行一些代码更改。一件好事是你有最初的优先权:

foreach (var command in _queue) 
{
    var priority = command.Key;
    var actualCommand = command.Value;
}