为何.NET Dictionary出现重复键?求问题排查思路
问题场景
类级别静态初始化的Dictionary<string, List<KafkaMessage>> StoredPoints,Keys集合中出现重复键"SNxxx_B",总计数为4但包含两个完全相同的该键。业务逻辑是从Kafka消费消息后,在DataSenderService.SendMessage方法中先判断StoredPoints是否包含目标键,不存在则添加键及空列表,随后将消息加入对应列表。已通过即时窗口和截图确认两个键内容完全一致,但Dictionary本身不允许重复键,这显然不符合预期。
可能的原因
多线程竞态条件
Dictionary不是线程安全的集合,当多个线程同时执行ContainsKey和Add操作时,会出现竞态:线程A判断键不存在,还没执行Add,线程B也完成了“键不存在”的判断,随后两个线程都执行Add操作,直接导致重复键写入,甚至可能破坏Dictionary内部的哈希结构,引发更严重的数据错乱。Dictionary内部结构损坏
多线程下对Dictionary进行无锁的修改(添加、删除元素),可能会破坏其内部哈希表的结构,导致Keys集合出现异常重复项,这种情况属于集合的“损坏状态”,后续操作可能引发更多不可预料的问题。字符串隐式差异(低概率)
虽然你确认键内容一致,但仍需排查是否存在不可见字符(比如全角空格、\0控制符)或大小写差异(默认Dictionary使用区分大小写的比较器)。可以通过string.Equals(key1, key2, StringComparison.Ordinal)或输出键的字节数组来验证。
解决方法
1. 改用线程安全集合(推荐)
使用ConcurrentDictionary<string, List<KafkaMessage>>替代普通Dictionary,它的GetOrAdd方法可以原子性完成“判断-添加”操作,彻底避免竞态条件:
// 静态初始化替换为线程安全集合 private static readonly ConcurrentDictionary<string, List<KafkaMessage>> StoredPoints = new ConcurrentDictionary<string, List<KafkaMessage>>(); // SendMessage方法内的逻辑修改 var messageList = StoredPoints.GetOrAdd(targetKey, _ => new List<KafkaMessage>()); // 注意:List本身非线程安全,多线程添加消息时需加锁或替换为ConcurrentBag lock (messageList) { messageList.Add(kafkaMessage); }
2. 为普通Dictionary添加全局锁
如果必须保留原有Dictionary,需要在所有访问、修改Dictionary的代码块外添加锁,确保同一时间只有一个线程操作集合:
private static readonly Dictionary<string, List<KafkaMessage>> StoredPoints = new Dictionary<string, List<KafkaMessage>>(); private static readonly object _dictLock = new object(); // SendMessage方法内的逻辑 lock (_dictLock) { if (!StoredPoints.ContainsKey(targetKey)) { StoredPoints.Add(targetKey, new List<KafkaMessage>()); } // 同样,List的Add操作如果涉及多线程,也需要加锁 lock (StoredPoints[targetKey]) { StoredPoints[targetKey].Add(kafkaMessage); } }
3. 修复并重启应用
如果Dictionary已经出现重复键的损坏状态,唯一的临时解决办法是重启应用,避免后续出现更严重的异常。同时必须立即修复线程安全问题,防止再次出现该情况。
4. 验证键的一致性
在代码中添加日志,输出可疑键的哈希值、长度以及字节数组,确认两个“相同”键是否真的完全一致:
// 示例日志代码 var keyBytes = Encoding.UTF8.GetBytes(targetKey); Console.WriteLine($"Key: {targetKey}, HashCode: {targetKey.GetHashCode()}, Length: {targetKey.Length}, Bytes: {BitConverter.ToString(keyBytes)}");
内容的提问来源于stack exchange,提问作者Malik Rizwan

