PriorityQueue在.NET 6+中是原生最小堆,按TPriority自然顺序排序,不支持更新已入队元素优先级,需用懒删除+重入队模拟;Dequeue总取最小优先级值,大数优先可传负值或自定义比较器;无遍历和查找能力,适合纯调度场景。

PriorityQueue
为什么 Dequeue() 总是取出最小数值优先级?
因为默认行为是 PriorityQueue 按 TPriority 类型的自然顺序(IComparer<tpriority>.Default</tpriority>)构建最小堆。对 int 来说,1 比 100 小,所以 1 被视为“更高优先级”,先出队。
- 想让大数优先?别改逻辑,直接传负值:
Enqueue("task", -priority) - 或显式传比较器:
new PriorityQueue<string int>(Comparer<int>.Create((a, b) => b.CompareTo(a)))</int></string> - 用
string当优先级时,按字典序升序排 ——"ERROR"比"INFO"靠前(因为'E' ),但注意大小写:<code>"warning"会排在"ERROR"后面
如何避免 Peek()/Dequeue() 报 InvalidOperationException?
常见错误是调用 Peek() 或 Dequeue() 前没检查空状态。这两个方法在队列为空时直接抛 InvalidOperationException,不返回 null 或 false。
- 安全做法:始终用
Count > 0判断,而不是Any()(PriorityQueue不实现IEnumerable) - 更推荐用
TryDequeue(out var item, out var priority):它返回bool,失败时不抛异常,适合循环出队场景 -
Peek()没有对应的TryPeek(),所以必须配Count > 0
怎样模拟“更新某个任务的优先级”?
原生 PriorityQueue 不提供 UpdatePriority(id, newPriority) —— 它没有索引、不支持查找、不能遍历。硬要更新,只能靠懒删除 + 重入队。
- 维护一个
Dictionary<taskid int> _currentPriority</taskid>记录每个任务当前有效优先级 - 入队时用
(taskId, payload)作TElement,原始优先级为TPriority - 更新只改字典:
_currentPriority[taskId] = newPriority - 出队时循环
TryDequeue(),并验证:if (_currentPriority[item.TaskId] == prio) { /* 处理 */ } else { /* 丢弃,继续 */ } - 注意:无效节点堆积太多会拖慢出队,建议当无效数 > 总数 30% 时重建队列
PriorityQueue 和 SortedSet 到底选哪个?
别只看“都能排序”,它们设计目标完全不同。如果你只需要“取最高/最低优先级项 + 入队 + 出队”,PriorityQueue 更轻、更快、内存更省。
-
PriorityQueue:无遍历能力,无条件删除,Enqueue均摊O(1),DequeueO(log n),适合纯调度场景 -
SortedSet:支持RemoveWhere、GetViewBetween、任意位置查找,但每次插入/删除都是O(log n),且元组重复会导致插入失败(相同(priority, item)被视为重复) - 如果需要按优先级范围批量处理(如“取出所有 priority ≤ 5 的任务”),
SortedSet是唯一选择;否则,优先用PriorityQueue
真正容易被忽略的点是:PriorityQueue 的“优先级”只是入队瞬间的一次快照,它不绑定元素生命周期,也不感知业务语义。你传进去的是什么值,它就忠实地比什么值——哪怕那个值在业务上早已过期。


















