Очень верно @Evk.
абочее предположение заключается в том, что LINQ является потокобезопасным при использовании сSystem.Collections.Concurrent коллекции (в том числеConcurrentDictionary).
(Другие сообщения переполнения, кажется, согласны:ссылка)
Тем не менее, проверка реализации LINQСортировать по метод расширения показывает, что он не является потокобезопасным с подмножеством параллельных коллекций, которые реализуютICollection (например.ConcurrentDictionary).
OrderedEnumerable GetEnumerator (источник здесь) создает экземплярбуфер структура (источник здесь) который пытается привести коллекцию кICollection (которыйConcurrentDictionary реализует), а затем выполняет collection.CopyTo с массивом, инициализированным по размеру коллекции.
Следовательно, еслиConcurrentDictionary (как бетонICollection в этом случае) увеличивается в размерах во время операции OrderBy, между инициализацией массива и копированием в него эта операция будет выброшена.
Следующий тестовый код показывает это исключение:
(Примечание: я ценю, что выполнениеСортировать по на поточно-безопасной коллекции, которая меняется под тобой, не так уж и многозначительно, но я не верю, что ее следует бросать)
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);
}
}
}
ЧтоСортировать по Бросок исключения приводит к одному из нескольких возможных выводов:
1) Мое предположение о том, что LINQ является потокобезопасным для одновременных коллекций, неверно, и безопасно выполнять LINQ только для коллекций (будь они одновременными или нет), которые не изменяются во время запроса LINQ.
2) Существует ошибка с реализацией LINQСортировать по и для реализации некорректно пытаться преобразовать исходную коллекцию в коллекцию ICollection и попытаться выполнить копию коллекции (и она должна просто перейти к своему поведению по умолчанию, повторяя IEnumerable).
3) Я неправильно понял, что здесь происходит ...
Мысли высоко ценится!