.NET ConcurrentDictionary.ToArray()ArgumentException

当我调用ConcurrentDictionary.ToArray时,有时会收到以下错误。 错误如下:

System.ArgumentException:索引等于或大于数组的长度,或者字典中的元素数大于从索引到目标数组末尾的可用空间。 在System.Collections.Concurrent.ConcurrentDictionary 2.System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair>.CopyTo(KeyValuePair在System.Linq.Buffer 1..ctor(IEnumerable 2.System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair>.CopyTo(KeyValuePair 2 []数组,Int32索引) System.Linq.Enumerable.ToArray [TSource](IEnumerable 1 source) at ...Cache.SlidingCache (IEnumerable 1 source) at ...Cache.SlidingCache (Object state)in … \ SlidingCache.cs:line 141 at在System.Threading.QuereadingUserWorkItemCallback.System上的System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback回调,Object状态,Boolean preserveSyncCtx)中的System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext,ContextCallback callback,Object state,Boolean preserveSyncCtx)。 System.Threading.ThreadPoolWorkQueue.Dispatch()中的Threading.IThreadPoolWorkItem.ExecuteWorkItem()

我注意到在multithreading场景中,有时在对ConcurrentDictionary进行排序时会出现exception。 请参阅堆栈溢出问题。 所以我在排序之前开始使用ConcurrentDictionary.ToArray。 在创建arrays时似乎仍然存在问题。

并发字典用于缓存,当达到缓存的设置的最大元素数时,该缓存维护对象并刷新最后访问的对象。 多个线程访问缓存,并且在尝试删除旧元素时会发生上述错误,因此可以将新元素添加到arrays中。 请参阅下面的一些代码段:

 public class SlidingCache : IDictionary { public int MinCount { get; private set; } public int MaxCount { get; private set; } private readonly IDictionary _cache = new ConcurrentDictionary(); public SlidingCache(int minCount=75000, int maxCount=100000) { if (minCount <= 2) throw new ArgumentException("minCount"); if (maxCount <= minCount) throw new ArgumentException("maxCount"); MinCount = minCount; MaxCount = maxCount; } #region IDictionary public int Count { get { return _cache.Count; } } public TValue this[TKey key] { get { return _cache[key].Value; } set { _cache[key]=new CacheValue(value); RemoveExcess(); } } ... #endregion private void RemoveExcess() { if (this.Count  i.Value.LastRequestTime).Take(MaxCount - MinCount); foreach (var pair in remove) { _cache.Remove(pair.Key); } Interlocked.Exchange(ref _removingExcess, 0); } 

任何人都可以解释上述exception和任何变通办法的潜在原因吗?

谢谢。

这是因为Enumerable.ToArray与并发集合一起使用是不安全的。

您应该将内部变量声明为ConcurrentDictionary类型而不是IDictionary ,因为这将使用字典本身实现的ToArray实现,而不是依赖于扩展方法:

 private readonly IDictionary _cache = new ConcurrentDictionary(); 

特别是, Enumerable.ToArray最终在内部使用Buffer类,这里是如何定义该类的构造函数(它的开头):

(来自Enumerable.cs – 参考源 )

 internal Buffer(IEnumerable source) { TElement[] items = null; int count = 0; ICollection collection = source as ICollection; if (collection != null) { count = collection.Count; if (count > 0) { items = new TElement[count]; collection.CopyTo(items, 0); } } 

如您所见,它使用字典的Count属性,创建一个数组,然后将元素复制到数组中。 如果基础字典在读取Count之后至少有一个其他项目,但在CopyTo之前,您就会遇到问题。

您可以将其与使用锁定的字典本身内的ToArray实现进行对比:

(来自ConcurrentDictionary.cs – 参考源 )

 public KeyValuePair[] ToArray() { int locksAcquired = 0; try { AcquireAllLocks(ref locksAcquired); int count = 0; checked { for (int i = 0; i < m_tables.m_locks.Length; i++) { count += m_tables.m_countPerLock[i]; } } KeyValuePair[] array = new KeyValuePair[count]; CopyToPairs(array, 0); return array; } finally { ReleaseLocks(0, locksAcquired); } }