.NET ConcurrentDictionary.ToArray() 参数异常
.NET ConcurrentDictionary.ToArray() ArgumentException
有时调用 ConcurrentDictionary.ToArray 时会出现以下错误。以下错误:
System.ArgumentException: The index is equal to or greater than the length of the array, or the number of elements in the dictionary is greater than the available space from index to the end of the destination array.
at System.Collections.Concurrent.ConcurrentDictionary2.System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>.CopyTo(KeyValuePair
2[] array, Int32 index)
at System.Linq.Buffer1..ctor(IEnumerable
1 source)
at System.Linq.Enumerable.ToArray[TSource](IEnumerable1 source)
at ...Cache.SlidingCache
2.RemoveExcessAsync(Object state) in ...\SlidingCache.cs:line 141
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
我注意到在多线程场景中,有时在对 ConcurrentDictionary 进行排序时会出现异常。请参阅堆栈溢出问题 here。所以我在排序之前开始使用 ConcurrentDictionary.ToArray 。创建数组时似乎仍然存在问题。
并发字典用于维护对象并在达到为缓存设置的最大元素数时刷新最后访问的对象的缓存。缓存由多个线程访问,并且在尝试删除旧元素以便可以将新元素添加到数组时发生上述错误。请查看下面的一些代码片段:
public class SlidingCache<TKey, TValue> : IDictionary<TKey, TValue>
{
public int MinCount { get; private set; }
public int MaxCount { get; private set; }
private readonly IDictionary<TKey, CacheValue> _cache = new ConcurrentDictionary<TKey, CacheValue>();
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<TKey, TValue>
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 <= this.MaxCount || Interlocked.Increment(ref _removingExcess) != 1)
return;
ThreadPool.QueueUserWorkItem(RemoveExcessAsync, null);
}
private int _removingExcess;
private void RemoveExcessAsync(object state)
{
var remove = _cache.ToArray().OrderByDescending(i => i.Value.LastRequestTime).Take(MaxCount - MinCount);
foreach (var pair in remove)
{
_cache.Remove(pair.Key);
}
Interlocked.Exchange(ref _removingExcess, 0);
}
任何人都可以解释上述异常的潜在原因和任何解决方法吗?
谢谢。
那是因为 Enumerable.ToArray
与并发集合一起使用是不安全的。
您应该将内部变量声明为 ConcurrentDictionary
类型而不是 IDictionary
,因为这将使用字典本身实现的 ToArray
实现,而不是依赖于扩展方法:
private readonly IDictionary<TKey, CacheValue> _cache = new ConcurrentDictionary<TKey, CacheValue>();
特别是,Enumerable.ToArray
最终在内部使用 Buffer
class,这里是 class 的构造函数的定义方式(它的开始) :
(来自Enumerable.cs - reference source)
internal Buffer(IEnumerable<TElement> source) {
TElement[] items = null;
int count = 0;
ICollection<TElement> collection = source as ICollection<TElement>;
if (collection != null) {
count = collection.Count;
if (count > 0) {
items = new TElement[count];
collection.CopyTo(items, 0);
}
}
如你所见,它使用字典的Count
属性,创建一个数组,然后将元素复制到数组中。如果基础字典在阅读 Count
之后但在 CopyTo
之前至少获得了一项其他项目,那么您就会遇到问题。
您可以将其与使用锁定的字典本身内部的 ToArray
实现进行对比:
(来自ConcurrentDictionary.cs - reference source)
public KeyValuePair<TKey, TValue>[] 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<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[count];
CopyToPairs(array, 0);
return array;
}
finally
{
ReleaseLocks(0, locksAcquired);
}
}
有时调用 ConcurrentDictionary.ToArray 时会出现以下错误。以下错误:
System.ArgumentException: The index is equal to or greater than the length of the array, or the number of elements in the dictionary is greater than the available space from index to the end of the destination array. at System.Collections.Concurrent.ConcurrentDictionary
2.System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>.CopyTo(KeyValuePair
2[] array, Int32 index) at System.Linq.Buffer1..ctor(IEnumerable
1 source) at System.Linq.Enumerable.ToArray[TSource](IEnumerable1 source) at ...Cache.SlidingCache
2.RemoveExcessAsync(Object state) in ...\SlidingCache.cs:line 141 at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx) at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() at System.Threading.ThreadPoolWorkQueue.Dispatch()
我注意到在多线程场景中,有时在对 ConcurrentDictionary 进行排序时会出现异常。请参阅堆栈溢出问题 here。所以我在排序之前开始使用 ConcurrentDictionary.ToArray 。创建数组时似乎仍然存在问题。
并发字典用于维护对象并在达到为缓存设置的最大元素数时刷新最后访问的对象的缓存。缓存由多个线程访问,并且在尝试删除旧元素以便可以将新元素添加到数组时发生上述错误。请查看下面的一些代码片段:
public class SlidingCache<TKey, TValue> : IDictionary<TKey, TValue>
{
public int MinCount { get; private set; }
public int MaxCount { get; private set; }
private readonly IDictionary<TKey, CacheValue> _cache = new ConcurrentDictionary<TKey, CacheValue>();
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<TKey, TValue>
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 <= this.MaxCount || Interlocked.Increment(ref _removingExcess) != 1)
return;
ThreadPool.QueueUserWorkItem(RemoveExcessAsync, null);
}
private int _removingExcess;
private void RemoveExcessAsync(object state)
{
var remove = _cache.ToArray().OrderByDescending(i => i.Value.LastRequestTime).Take(MaxCount - MinCount);
foreach (var pair in remove)
{
_cache.Remove(pair.Key);
}
Interlocked.Exchange(ref _removingExcess, 0);
}
任何人都可以解释上述异常的潜在原因和任何解决方法吗?
谢谢。
那是因为 Enumerable.ToArray
与并发集合一起使用是不安全的。
您应该将内部变量声明为 ConcurrentDictionary
类型而不是 IDictionary
,因为这将使用字典本身实现的 ToArray
实现,而不是依赖于扩展方法:
private readonly IDictionary<TKey, CacheValue> _cache = new ConcurrentDictionary<TKey, CacheValue>();
特别是,Enumerable.ToArray
最终在内部使用 Buffer
class,这里是 class 的构造函数的定义方式(它的开始) :
(来自Enumerable.cs - reference source)
internal Buffer(IEnumerable<TElement> source) {
TElement[] items = null;
int count = 0;
ICollection<TElement> collection = source as ICollection<TElement>;
if (collection != null) {
count = collection.Count;
if (count > 0) {
items = new TElement[count];
collection.CopyTo(items, 0);
}
}
如你所见,它使用字典的Count
属性,创建一个数组,然后将元素复制到数组中。如果基础字典在阅读 Count
之后但在 CopyTo
之前至少获得了一项其他项目,那么您就会遇到问题。
您可以将其与使用锁定的字典本身内部的 ToArray
实现进行对比:
(来自ConcurrentDictionary.cs - reference source)
public KeyValuePair<TKey, TValue>[] 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<TKey, TValue>[] array = new KeyValuePair<TKey, TValue>[count];
CopyToPairs(array, 0);
return array;
}
finally
{
ReleaseLocks(0, locksAcquired);
}
}