我认为这里直接问题的答案是“不,没有”。但我有同样的潜在场景问题。以下是我的解决方法。
TL;DR 我使用了一个 BlockingCollections 数组,每个数组元素用于不同的优先级。这似乎是最简单的解决方案。它与可用的 BlockingCollection 方法完美契合。
我遇到了同样的挑战:偶尔会添加更高优先级元素的队列。当有更高优先级的元素时,需要在所有正常优先级元素之前处理它们。较高优先级元素和普通优先级元素都需要在其优先级桶内分别按照先进先出的顺序进行处理。对于如何使用处理的灵活性,理想情况下优先级不应是有限的小集合,但正如您将在下面看到的那样,我最终妥协了这一需求。
我最终得到的代码:
在类声明中。这假设我们需要 4 个优先级:
BlockingCollection<ImageToLoad>[] imagesToLoad = new BlockingCollection<ImageToLoad>[4];
在类构造函数中(使用精美的 LINQ 语法创建 BlockingCollection 的 4 个实例 - 坦率地说,当它写成 0->3 for 循环时更容易阅读:-)):
imagesToLoad = Enumerable.Repeat(0, 4).Select(bc => new BlockingCollection<ImageToLoad>()).ToArray();
当一个元素被添加到工作队列中时:
imagesToLoad[priority].Add(newImageToLoad);
worker 任务,挑选优先级高的任务(4 比 3 高,等等),当工作到达 4 个优先队列中的任何一个时,从它的非工作队列块中唤醒:
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);
}
由 FIFO ConcurrentQueue 支持的 BlockingCollection 是一个很好的基础。它允许您执行 FirstOrDefault(Func predicate) 来查找具有更高优先级的项目。但是,正如在这个问题中提到的,它没有一个 Remove(Func predicate) 或一个 Take(Func predicate) 方法来在它们被无序处理时从队列中取出更高优先级的项目。
我考虑了 4 种解决方案,按照工作量和复杂性的递减顺序排列:
A. 编写我自己的 BlockingCollection 版本。这是最优雅的解决方案。当您只需要 Add、Take 和 Take(Func 谓词)时,我认为复杂性不会太差,但这需要时间,并且至少会有一些难以找到的并发或锁定错误.在等待新元素到达时,我会使用 SemaphoreSlim 来处理阻塞。我可能会使用 OrderedDictionary 来保留元素。
B. 放弃 BlockingCollection。直接使用 OrderedDictionary(类似于 David 上面建议的 ConcurrentDictionary)。逻辑与选项 A 非常相似,只是没有封装在单独的类中。
C. 这是一个 hack:向正在排队的元素添加一个属性,以指示该元素是否已被处理。当你处理一个元素由于它具有更高的优先级而乱序时,将此属性设置为“已经完成了这个”。当您稍后获取此元素时,因为它恰好成为队列前面的元素,只需丢弃它(而不是处理它,就像您对队列前面的元素所做的那样,这些元素尚未被处理)。但是这会变得复杂,因为:(1) 在许多情况下,您最终不得不处理这些实际上不属于 BlockingCollection 的元素。 (2) 从并发的角度来看令人困惑,部分依赖于 BlockingCollection 和 BlockingCollection.Take() 中的锁,部分依赖于我需要添加的锁来执行围绕查找高优先级项目和更新它们的逻辑。由于两个单独的锁之间的冲突,很容易导致死锁。
D. 与其让优先级的数量无限制,不如将其锁定为 2 或 3,或者我认为我可能需要的任何值,并创建许多单独的 BlockingCollections。然后,您可以使用 TakeFromAny 调用进行阻止。当不阻塞时(因为当您准备处理另一个元素时已经有可用的元素),您可以检查每个 BlockingCollection 并从非空的最高优先级中获取。
在意识到选项 (C) 造成的所有复杂性之前,我花了相当多的时间尝试使选项 (C) 起作用。
我最终选择了选项 (D)。
让优先级的数量动态化是很简单的,所以它是在创建类时设置的。但是,使用这种方法使其具有与任何 int 一样的优先级完全运行时动态是不切实际的,因为会创建很多未使用的 BlockingCollection 对象。