Подтвердить что ты не робот

Порядок элементов в BlockingCollection <>

У меня есть очередь загрузки, реализованная с помощью BlockingCollection<>. Теперь я хочу приоритизировать некоторые загрузки раз в то время. Я подумал, что было бы здорово переместить некоторые элементы в "Сбор", как в списке, но нет такого метода, как Remove()/AddFirst() или Move().

Какой предпочтительный способ размещения элементов в BlockingCollection<>?

4b9b3361

Ответ 1

BlockingCollection<T> работает, обертывая внутренний IProducerConsumerCollection<T>. По умолчанию используется ConcurrentQueue<T> внутри, но вы можете предоставить свою собственную реализацию через этот конструктор.

Если вы предоставляете собственную коллекцию потоков, вы можете использовать любой тип коллекции, который вы хотите. Это позволит вам определять приоритеты элементов по мере необходимости.

В то время как нет встроенных коллекций, которые будут реализовывать желаемую функциональность, вы могли бы, вероятно, обернуть пару коллекций ConcurrentQueue<T> в класс, который реализует IProducerConsumerCollection<T>. Это позволит вам иметь элементы с высоким приоритетом и низким приоритетом.

Ответ 2

К сожалению, нет способа переупорядочить очередь так, как вы хотите. Что вам действительно нужно, это 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

Это заставляет вас использовать несколько очередей, поэтому это не самое элегантное решение.

Ответ 3

Невозможно реализовать приоритетную очередь непосредственно поверх BlockingCollection<T>. A 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. Это то, что вам нужно будет написать на основе приоритетного планирования загружаемых элементов.

Ответ 4

Рид прав, говоря вам, что вам нужно реализовать 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;
}