Я думаю, что ответ на прямой вопрос здесь "нет, нет". Но у меня была та же основная проблема сценария. Вот как я это решил.
TL; DR Я использовал массив BlockingCollections, где каждый элемент массива имеет свой уровень приоритета. Это кажется самым простым решением. Идеально подходит для доступных методов BlockingCollection.
У меня была такая же проблема: очередь, в которую иногда добавляются элементы с более высоким приоритетом. Когда есть элементы с более высоким приоритетом, они должны обрабатываться перед всеми элементами с обычным приоритетом. Как элементы с более высоким приоритетом, так и элементы с обычным приоритетом должны обрабатываться соответственно в порядке FIFO в пределах их приоритетных сегментов. Для гибкости в использовании обработки уровни приоритетов в идеале не должны быть конечным небольшим набором, но, как вы увидите ниже, я в итоге пошел на компромисс в этой необходимости.
Код, с которым я закончил:
В объявлении класса. Это предполагает, что нам нужно 4 уровня приоритета:
BlockingCollection<ImageToLoad>[] imagesToLoad = new BlockingCollection<ImageToLoad>[4];
В конструкторе класса (чтобы создать 4 экземпляра BlockingCollection с причудливым синтаксисом LINQ - откровенно проще читать, когда просто записывается как цикл 0-> 3 для :-)):
imagesToLoad = Enumerable.Repeat(0, 4).Select(bc => new BlockingCollection<ImageToLoad>()).ToArray();
Когда элемент добавляется в рабочую очередь:
imagesToLoad[priority].Add(newImageToLoad);
Рабочая задача, выбирающая задачи с высоким значением prio (4 выше, чем prio, чем 3 и т. Д.), И просыпающаяся из своего блока без очереди, когда работа поступает в любую из 4 очередей prio:
while (true)
{
ImageToLoad nextImageToLoad = null;
for (int i = 3; i >= 0; i--)
if (imagesToLoad[i].TryTake(out nextImageToLoad))
break;
if (nextImageToLoad == null)
BlockingCollection<ImageToLoad>.TakeFromAny(imagesToLoad, out nextImageToLoad);
if (nextImageToLoad != null)
await LoadOneImage(nextImageToLoad);
}
BlockingCollection, поддерживаемая FIFO ConcurrentQueue, является отличной основой для этого. Это позволяет вам сделать FirstOrDefault (предикат Func), чтобы найти элементы с более высоким приоритетом. Но, как уже упоминалось в этом вопросе, в нем нет методов Remove (предикат Func) или Take (предикат Func), чтобы вывести элементы с более высоким приоритетом из очереди, когда они были обработаны не по порядку.
Я рассмотрел 4 решения в порядке убывания работы и сложности:
А. Написание моей собственной версии BlockingCollection. Это самое элегантное решение. Я не думаю, что это будет слишком плохо по сложности, когда вам нужны только Add, Take и Take (предикат Func), но это займет время, и будут по крайней мере некоторые ошибки параллелизма или блокировки, которые будет трудно найти , Я бы использовал SemaphoreSlim для обработки блокировки во время ожидания поступления новых элементов. Я бы, вероятно, использовал OrderedDictionary, чтобы сохранить элементы.
Б. Откажись от BlockingCollection. Используйте OrderedDictionary <> напрямую (аналогично тому, что Дэвид предложил выше с ConcurrentDictionary). Логика была бы очень похожа на вариант А, только не заключенная в отдельный класс.
C. Это что-то вроде хака: добавьте свойство к элементам, попадающим в очередь, чтобы указать, был ли элемент уже обработан. Когда вы обрабатываете элемент не по порядку из-за его более высокого приоритета, установите для этого свойства значение «уже сделано с этим». Когда вы позже возьмете этот элемент, потому что он стал тем, что находится в начале очереди, просто отбросьте его (вместо того, чтобы обрабатывать его, как вы сделали бы с элементами, которые вы берете в начале очереди, которые еще не были обработанный). Это становится сложным, потому что: (1) вам в конечном итоге придется обрабатывать эти элементы, которые на самом деле не входят в BlockingCollection, особенно во многих контекстах. (2) путаница с точки зрения параллелизма, чтобы частично полагаться на блокировки внутри BlockingCollection и BlockingCollection.Take (), а частично на блокировки, которые мне нужно добавить, чтобы выполнить логику поиска элементов высокого уровня и их обновления. Это будет слишком легко привести к тупикам из-за конфликтов между двумя отдельными блокировками.D. Вместо того, чтобы разрешить открывать число уровней приоритета, установите для него значение 2 или 3, или, как мне кажется, мне может понадобиться, и создайте такое количество отдельных BlockingCollections. Затем вы можете сделать блокировку с помощью вызова TakeFromAny. Если блокирование не выполняется (поскольку элементы уже доступны, когда вы готовы обработать другой), вы можете проверить каждую коллекцию BlockingCollection и Take из элемента с наивысшим приоритетом, который не пуст.
Я потратил немало времени, пытаясь заставить работать опцию (C), прежде чем осознал всю сложность, которую он создавал.
В итоге я выбрал вариант (D).
Было бы просто сделать динамическое количество уровней приоритета, чтобы оно устанавливалось при создании класса. Но делать это полностью динамически динамическим с приоритетами, так как любое int нецелесообразно с таким подходом, поскольку будет создано очень много неиспользуемых объектов BlockingCollection.