.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.Buffer1..ctor(IEnumerable
2.System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair>.CopyTo(KeyValuePair
2 []数组,Int32索引) System.Linq.Enumerable.ToArray [TSource](IEnumerable1 source) at ...Cache.SlidingCache
(IEnumerable1 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); } }