¿C # LINQ OrderBy es seguro para subprocesos cuando se usa con ConcurrentDictionary <Tkey, TValue>?
Mi suposición de trabajo es que LINQ es seguro para subprocesos cuando se usa con elSystem.Collections.Concurrent colecciones (incluyendoConcurrenteDiccionario)
(Otras publicaciones de desbordamiento parecen estar de acuerdo:enlazar)
Sin embargo, una inspección de la implementación de LINQOrderBy El método de extensión muestra que parece no ser seguro para subprocesos con el subconjunto de colecciones concurrentes que implementanICollection (p.ej.ConcurrenteDiccionario)
losPedido Enumerable GetEnumerator (fuente aquí) construye una instancia de unBuffer struct (fuente aquí) que intenta convertir la colección en unICollection (cualConcurrenteDiccionario implementa) y luego realiza una colección.CopyTo con una matriz inicializada al tamaño de la colección.
Por lo tanto, si elConcurrenteDiccionario (como el hormigónICollection en este caso) aumenta de tamaño durante la operación OrderBy, entre la inicialización de la matriz y la copia en ella, esta operación arrojará.
El siguiente código de prueba muestra esta excepción:
(Nota: aprecio que realizar unOrderBy en una colección segura para subprocesos que está cambiando debajo de ti no es tan significativa, pero no creo que deba arrojarse)
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 elOrderBy lanza una excepción lleva a una de las pocas conclusiones posibles:
1) Mi suposición acerca de que LINQ es seguro para subprocesos con colecciones simultáneas es incorrecta, y solo es seguro realizar LINQ en colecciones (sean concurrentes o no) que no están mutando durante la consulta LINQ
2) Hay un error con la implementación de LINQOrderBy y es incorrecto que la implementación intente convertir la colección de origen a una ICollection e intente y realice la copia de la colección (y debería pasar a su comportamiento predeterminado iterando IEnumerable).
3) He entendido mal lo que está pasando aquí ...
Pensamientos muy apreciados!