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!