C # LINQ OrderBy threadsafe quando usado com ConcurrentDictionary <Tkey, TValue>?

Minha suposição de trabalho é que o LINQ é seguro para threads quando usado com oSystem.Collections.Concurrent coleções (incluindoConcurrentDictionary)

(Outras postagens do estouro parecem concordar:ligação)

No entanto, uma inspeção da implementação do LINQOrdenar por O método de extensão mostra que parece não ser seguro com o subconjunto de coleções simultâneas que implementamICollection (por exemplo.ConcurrentDictionary)

oOrderedEnumerable GetEnumerator (fonte aqui) constrói uma instância de umAmortecedor struct (fonte aqui) que tenta converter a coleção em umICollection (qualConcurrentDictionary implementa) e, em seguida, executa uma collection.CopyTo com uma matriz inicializada com o tamanho da coleção.

Portanto, se oConcurrentDictionary (como o concretoICollection nesse caso) cresce de tamanho durante a operação OrderBy, entre a inicialização do array e a cópia nele, essa operação será lançada.

O código de teste a seguir mostra essa exceção:

(Nota: eu aprecio o fato de executar umOrdenar por em uma coleção thread-safe que está mudando embaixo de você não é tão significativa, mas não acredito que deva ser lançada)

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Program
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                int loop = 0;
                while (true) //Run many loops until exception thrown
                {
                    Console.WriteLine($"Loop: {++loop}");

                    _DoConcurrentDictionaryWork().Wait();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        private static async Task _DoConcurrentDictionaryWork()
        {
            var concurrentDictionary = new ConcurrentDictionary<int, object>();
            var keyGenerator = new Random();
            var tokenSource = new CancellationTokenSource();

            var orderByTaskLoop = Task.Run(() =>
            {
                var token = tokenSource.Token;
                while (token.IsCancellationRequested == false)
                {
                    //Keep ordering concurrent dictionary on a loop
                    var orderedPairs = concurrentDictionary.OrderBy(x => x.Key).ToArray(); //THROWS EXCEPTION HERE

                    //...do some more work with ordered snapshot...
                }
            });

            var updateDictTaskLoop = Task.Run(() =>
            {
                var token = tokenSource.Token;
                while (token.IsCancellationRequested == false)
                {
                    //keep mutating dictionary on a loop
                    var key = keyGenerator.Next(0, 1000);
                    concurrentDictionary[key] = new object();
                }
            });

            //Wait for 1 second
            await Task.Delay(TimeSpan.FromSeconds(1));

            //Cancel and dispose token
            tokenSource.Cancel();
            tokenSource.Dispose();

            //Wait for orderBy and update loops to finish (now token cancelled)
            await Task.WhenAll(orderByTaskLoop, updateDictTaskLoop);
        }
    }
}

Que oOrdenar por lança uma exceção leva a uma das poucas conclusões possíveis:

1) Minha suposição de que o LINQ é seguro para threads com coleções simultâneas está incorreto e é seguro executar LINQ apenas em coleções (sejam simultâneas ou não) que não estão sofrendo mutações durante a consulta LINQ

2) Há um erro na implementação do LINQOrdenar por e está incorreto para a implementação tentar converter a coleção de origem em um ICollection e tentar executar a cópia da coleção (e deve apenas retornar ao seu comportamento padrão que itera o IEnumerable).

3) Eu entendi mal o que está acontecendo aqui ...

Pensamentos muito apreciados!

questionAnswers(1)

yourAnswerToTheQuestion