SortedList 和 SortedDictionary 默认非线程安全,多线程读写会引发异常或数据不一致;加锁需覆盖所有访问路径且防死锁;推荐用 ConcurrentDictionary 配合手动排序实现安全有序操作。

SortedList 和 SortedDictionary 默认都不是线程安全的
直接在多线程环境下读写同一个 SortedList 或 SortedDictionary 实例,会触发 InvalidOperationException(如“集合已修改;枚举操作可能无法执行”),或产生数据不一致、索引越界等未定义行为。.NET 官方文档明确标注二者均 不保证线程安全,即使只读访问多个线程同时遍历,也可能因底层结构被其他线程修改而失败。
为什么不能靠加锁“简单包一层”就高枕无忧
加锁能避免崩溃,但容易忽略两个关键点:
- 所有访问路径(包括
ContainsKey、TryGetValue、Keys、Values、GetEnumerator())都必须使用同一把锁,漏掉任意一个就可能出问题 -
Keys和Values属性返回的是动态视图,不是快照——如果在遍历myDict.Keys时另一个线程修改了字典,迭代器立刻失效 - 嵌套调用(比如在锁内调用一个外部方法,而该方法又间接访问了同一个字典)可能引发死锁或锁粒度失控
常见错误写法:
lock (_lock) {
if (dict.ContainsKey(key)) { // ✅ 加锁
dict[key] = value; // ✅ 加锁
}
}
// 但下面这行没锁,且返回的 ICollection 不是线程安全的:
var keys = dict.Keys.ToList(); // ❌ 可能中途被改,ToList() 过程中抛异常
真正安全的替代方案:ConcurrentDictionary + 手动排序逻辑
ConcurrentDictionary 是唯一内置线程安全的键值集合,但它不维持顺序。若业务强依赖有序遍历(如按 key 升序取前 N 项),只能放弃自动排序,改用以下组合:
千博购物系统.Net能够适合不同类型商品,为您提供了一个完整的在线开店解决方案。千博购物系统.Net除了拥有一般网上商店系统所具有的所有功能,还拥有着其它网店系统没有的许多超强功能。千博购物系统.Net适合中小企业和个人快速构建个性化的网上商店。强劲、安全、稳定、易用、免费是它的主要特性。系统由C#及Access/MS SQL开发,是B/S(浏览器/服务器)结构Asp.Net程序。多种独创的技术使
- 用
ConcurrentDictionary存储数据,保障并发读写安全 - 需要有序结果时,临时提取
Keys或ToArray(),再用OrderBy()或Array.Sort()排序(注意:这是快照,不影响原集合) - 若频繁按范围查询(如 “key ∈ [a,b)”),可额外维护一个
ConcurrentBag+ 定期重建排序数组,或引入第三方库如System.Collections.Concurrent.ConcurrentSortedList(非 .NET 内置,需 NuGet)
示例(安全取最小 key 对应的 value):
var keys = concurrentDict.Keys.ToArray();
if (keys.Length > 0) {
var minKey = keys.Min(); // 或 Array.Sort(keys); var minKey = keys[0];
concurrentDict.TryGetValue(minKey, out var value);
}
SortedDictionary 在高并发写入下的性能陷阱
虽然 SortedDictionary 基于红黑树,插入/查找平均 O(log n),但它的内部节点操作不是原子的。多线程写入时,即使加了锁,也会因锁竞争导致吞吐量急剧下降——尤其当写操作占比超过 20%,性能可能比单线程还差。相比之下,ConcurrentDictionary 的分段锁机制在写密集场景下更稳定。
容易被忽略的一点:SortedList 在大量插入后内存碎片更严重(底层是数组),扩容时需复制整个数组,此时锁持有时间变长,进一步加剧争用。除非你确定是读多写少 + 数据量小(IList









