gpt4 book ai didi

c# - .NET ConcurrentDictionary.ToArray() 参数异常

转载 作者:可可西里 更新时间:2023-11-01 09:03:01 29 4
gpt4 key购买 nike

有时,当我调用 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(KeyValuePair2[] array, Int32 index) at System.Linq.Buffer1..ctor(IEnumerable1 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 类,下面是该类的构造函数是如何定义的(它的开头):

(来自 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);
}
}

关于c# - .NET ConcurrentDictionary.ToArray() 参数异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29648849/

29 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com