.NET ConcurrentDictionary.ToArray()ArgumentException [英] .NET ConcurrentDictionary.ToArray() ArgumentException

查看:90
本文介绍了.NET ConcurrentDictionary.ToArray()ArgumentException的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

有时,当我调用ConcurrentDictionary.ToArray时,出现以下错误.错误如下:

Sometimes I get the error below when I call ConcurrentDictionary.ToArray. Error Below:

System.ArgumentException:索引等于或大于数组的长度,或者字典中的元素数大于从索引到目标数组末尾的可用空间. 在System.Collections.Concurrent.ConcurrentDictionary 2.System.Collections.Generic.ICollection<System.Collections.Generic.KeyValuePair<TKey,TValue>>.CopyTo(KeyValuePair 2 []数组,Int32索引处) 在System.Linq.Buffer 1..ctor(IEnumerable 1源上) 在System.Linq.Enumerable.ToArray [TSource](IEnumerable 1 source) at ...Cache.SlidingCache 2.RemoveExcessAsync(Object state)in ... \ SlidingCache.cs:line 141中 在System.Threading.ExecutionContext.RunInternal(ExecutionContext executeContext,ContextCallback回调,对象状态,布尔类型saveSyncCtx) 在System.Threading.ExecutionContext.Run(ExecutionContext执行上下文,ContextCallback回调,对象状态,布尔类型saveSyncCtx) 在System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem() 在System.Threading.ThreadPoolWorkQueue.Dispatch()

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.SlidingCache2.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排序时有时会出现异常.请参见此处.因此,我开始在排序之前使用ConcurrentDictionary.ToArray.似乎在创建数组时仍然存在问题.

I noticed that in multithreaded scenarios you sometimes get exceptions when sorting the ConcurrentDictionary. See stack overflow question here. So I started using ConcurrentDictionary.ToArray before sorting instead. It appears there are still problems while creating the array as well.

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

The concurrent dictionary is being used for a cache that maintains objects and flushes the last accessed objects when the set maximum number of elements for the cache is reached. The cache is accessed by multiple threads and the above error occurs when trying to remove older elements so new elements can be added to the array. Please see some code snippets below:

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);
    }

任何人都可以请您解释上述异常的潜在原因以及任何解决方法吗?

Can anyone kindly explain the potential reason for the above exception and any workarounds?

谢谢.

推荐答案

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

That is because Enumerable.ToArray is not safe to use with concurrent collections.

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

You should declare your internal variable to be of type ConcurrentDictionary and not IDictionary, as this would use the ToArray implementation implemented by the dictionary itself, instead of relying on the extension method:

private readonly IDictionary<TKey, CacheValue> _cache = new ConcurrentDictionary<TKey, CacheValue>();

特别是,Enumerable.ToArray最终在内部使用了Buffer类,这是该类的构造函数(其开始)的定义方式:

In particular, Enumerable.ToArray ends up using a Buffer class internally, and here is how the constructor of that class is defined (the start of it):

(来自 Enumerable.cs-参考源)

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之前至少还有其他一项,则您遇到了问题.

As you can see, it uses the Count property of the dictionary, creates an array, then copies the elements to the array. If the underlying dictionary has gotten at least one other item after reading Count but before CopyTo you get your problem.

您可以将其与使用锁定的字典本身内部的ToArray实现相反:

You can contrast that with the implementation of ToArray inside the dictionary itself which uses locking:

(来自 ConcurrentDictionary.cs-参考源)

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);
    }
}

这篇关于.NET ConcurrentDictionary.ToArray()ArgumentException的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆