向通用字典添加内容导致IndexOutOfRangeException异常

37
我正在某个任务中使用一个字典。
逻辑上,我已经设置好了键值不会冲突,但有时候当我往字典中添加内容时,会出现这个异常。
Index was outside the bounds of the array.
at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   at Rpc.<MapIntoRpc>b__4[T](Object x) in Rpc.cs:line 113
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)
   at System.Threading.Tasks.Task.InnerInvoke()
   at System.Threading.Tasks.Task.Execute()

我了解尝试多次删除或添加同一键可能会出现并发问题,但我已经在算法上考虑到了这一点。
是什么导致添加有时会失败? 有什么最好的方法来解决这个问题?

1
你能发一下导致异常的代码吗? - Brad M
1
你是否从多个线程中添加它?很可能是同步问题。 - NominSim
3
考虑使用ConcurrentDictionary,而不是尝试自己编写相应的线程安全(无锁?)代码... - Alexei Levenkov
我不能发布导致异常的代码,但我正在从多个线程(任务)添加并从多个线程(任务)删除。 - Austin Harris
1
我理解在尝试多次添加或删除相同的键时可能会出现并发问题。我认为对于修改操作(无论键是否不同),都存在并发问题。我认为标准容器只有读取操作是线程安全的。 - Matt Smith
啊,对集合的任何修改都会引起问题..明白了。 - Austin Harris
3个回答

59
你应该查看文档。它的意思是:
一个字典可以支持多个读者并发访问,只要不修改集合。即便如此,枚举整个集合本质上也不是线程安全的过程。在罕见的情况下,当枚举与写入访问相互竞争时,必须在整个枚举期间锁定集合。为了允许多个线程对集合进行读写访问,您必须实现自己的同步。如果需要线程安全的替代方案,请参阅ConcurrentDictionary。

2
我现在正在使用ConcurrentDictionary。谢谢! - Austin Harris

25
您的问题很可能是同步问题。当添加一个字典时,有时需要增加底层结构(数组)的大小。如果您从多个线程中添加,可能会导致IndexOutOfRangeException。您需要使用锁等来确保以安全的方式添加。
或者,您可以使用ConcurrentDictionary,这是一个线程安全的集合。

谢谢@NominSim - 这个解释非常有帮助 :) 我也不知道ConcurrentDictionary - 干杯! - m.t.bennett

19

也许你认为“随便!就算有一次出错了”-但不行:

重要提示:一旦字典损坏,它就无法使用!

由于为了调试而添加的一个字典从未被读取,竟然导致三个小时的销售记录丢失(直到IIS按计划重新启动)。

输入图像描述

注意:在我遇到这种情况之前已经运行了3.5年。

 private Dictionary<string, string> _debugLookup;

 _debugLookup[key] = virtualPath;

这甚至不是一个静态的字典 - 它是一个MVC IViewLocationCache,它是一个实例方法。


2
我也遇到了同样的问题。在多线程环境中共享一个 HttpClient 对象。如果你需要添加/删除头信息(只是一个 Dictionary)... 它会进入你上面发布的状态。我以为 HttpClient 是线程安全的!但实际上并不是。 - Andy
面对同样的问题,但是由于无法更改字典,我正在实现一个接口,并返回具有该字典的类。https://stackoverflow.com/questions/60105094/getting-indexoutofrangeexception-in-modelclientvalidationrule - Seichi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接