.NET ConcurrentDictionary.ToArray() ArgumentException

前端 未结 1 2051
后悔当初
后悔当初 2021-01-11 14:55

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

System.ArgumentException: The index is equal to or greater than

相关标签:
1条回答
  • 2021-01-11 15:11

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

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

    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):

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

    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.

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

    (from 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);
        }
    }
    
    0 讨论(0)
提交回复
热议问题