Giả định làm việc của tôi là LINQ là an toàn luồng khi được sử dụng với các bộ sưu tập System.Collections.Concurrent (bao gồm ConcurrentDictionary).Có phải C# LINQ OrderBy threads an toàn khi được sử dụng với ConcurrentDictionary <Tkey, TValue>?
(bài viết Overflow khác cũng đồng ý với link)
Tuy nhiên, một thanh tra việc thực hiện của LINQ OrderBy phương pháp khuyến nông cho thấy rằng nó không xuất hiện để được thread với các tập hợp con của các bộ sưu tập đồng thời mà thực hiện ICollection (ví dụ: ConcurrentDictionary).
Các OrderedEnumerableGetEnumerator (source here) xây dựng một thể hiện của một Buffer struct (source here) mà cố gắng để đúc các bộ sưu tập để một ICollection (mà ConcurrentDictionary dụng cụ) và sau đó thực hiện một bộ sưu tập. CopyTo với một mảng được khởi tạo với kích thước của bộ sưu tập.
Do đó, nếu ConcurrentDictionary (như bê tông ICollection trong trường hợp này) phát triển về kích thước trong khi phẫu thuật OrderBy, giữa initialising mảng và sao chép vào nó, hoạt động này sẽ ném.
Các mã kiểm tra dưới đây cho thấy ngoại lệ này:
(Lưu ý: Tôi đánh giá cao mà thực hiện một OrderBy trên một bộ sưu tập thread-safe đó đang thay đổi bên dưới bạn mà không phải là có ý nghĩa, nhưng tôi không tin điều đó nên ném)
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);
}
}
}
Đó là OrderBy ném một ngoại lệ dẫn đến một trong số ít những kết luận có thể:
1) Giả định của tôi về LINQ là luồng an toàn với các bộ sưu tập đồng thời là không chính xác, và nó chỉ an toàn để thực hiện LINQ trên các bộ sưu tập (chúng đồng thời hay không) mà không bị đột biến trong truy vấn LINQ
2) Có lỗi với việc triển khai LINQ OrderBy và việc triển khai thử nghiệm và truyền tập hợp nguồn đến ICollection là không chính xác và thử và thực hiện sao chép bộ sưu tập (và nó chỉ cần chuyển sang hành vi mặc định của nó lặp lại IEnumerable).
3) Tôi đã hiểu lầm những gì đang xảy ra ở đây ...
Suy nghĩ được đánh giá cao!
_ "NGOẠI TRỪ NÀY" _ Ngoại lệ là gì? –
Giả định của bạn rằng hàm tạo bộ đệm tạo thành 'ICollection' sẽ thành công là sai. Bởi vì ['ConcurrentDictionary.GetEnumerator'] (https://referencesource.microsoft.com/#mscorlib/system/Collections/Concurrent/ConcurrentDictionary.cs,b2dcb93f9ede4ba0,references) là nguồn thực tế và đây không phải là một' ICollection '. –
Có vẻ như bạn đã trả lời câu hỏi của riêng mình, phải không? Ý tôi là bạn thấy mình không an toàn để làm điều này. – Evk