Paralel programlamada en sık yapılan hata, standart List<T> veya Dictionary<K,V> koleksiyonlarını birden fazla thread ile aynı anda manipüle etmeye çalışmaktır. Sonuç? Ya IndexOutOfRangeException ya da verinin sessizce kaybolması.
Senior bir geliştirici, tekerleği yeniden icat etmek (yani her yere lock koymak) yerine, bu iş için optimize edilmiş Concurrent Collections yapılarını kullanır.
1. Neden Standart Koleksiyonlar Yetmiyor?
Standart bir List<T> üzerine paralel ekleme yaptığınızda, koleksiyonun iç kapasitesi (internal array) dolup yeniden boyutlandırılmaya (resize) çalışıldığında iki thread birbirinin üzerine yazar.
// TEHLİKELİ KOD: Bu liste muhtemelen bozulacak veya eksik veri tutacak.
var list = new List<int>();
Parallel.For(0, 1000, i => list.Add(i));2. ConcurrentDictionary: Performans Canavarı
Eğer çok thread'li bir ortamda cache veya sözlük yapısı yönetiyorsanız, ConcurrentDictionary<TKey, TValue> hayat kurtarır. En büyük avantajı, tüm koleksiyonu kilitlemek yerine Fine-grained locking (parçalı kilitleme) kullanarak sadece ilgili "bucket"ı kilitlemesidir.
var cache = new ConcurrentDictionary<int, string>();
// Ekleme veya Güncelleme (Atomic)
// Eğer anahtar yoksa ekler, varsa mevcut değeri günceller.
cache.AddOrUpdate(1, "İlk Değer", (key, oldVal) => "Güncellenmiş Değer");
// Güvenli Okuma ve Ekleme
string value = cache.GetOrAdd(2, (key) => FetchFromDatabase(key));3. ConcurrentBag ve ConcurrentQueue: Hangisi Ne Zaman?
ConcurrentQueue<T>: "İlk giren ilk çıkar" (FIFO) kuralı asenkron dünyada da bozulmasın istiyorsanız kullanılır. Özellikle Producer-Consumer desenleri için idealdir.ConcurrentBag<T>: Eğer elemanların sırası önemli değilse ve aynı thread hem ekleme hem okuma yapıyorsa (work-stealing senaryoları),ConcurrentBagen hızlısıdır.
var orders = new ConcurrentQueue<string>();
// Producer (Üretici)
Parallel.For(0, 100, i => orders.Enqueue($"Sipariş-{i}"));
// Consumer (Tüketici)
while (orders.TryDequeue(out string order))
{
Console.WriteLine($"İşleniyor: {order}");
}4. BlockingCollection: Gerçek Bir Orkestra Şefi
Bir Senior Developer'ın favori araçlarından biri BlockingCollection<T>'dır. Bir kanal (channel) gibi davranır; eğer koleksiyon boşsa tüketiciyi bekletir, koleksiyon doluysa üreticiyi yavaşlatır.
// Maksimum 10 eleman alabilen bir buffer
var buffer = new BlockingCollection<int>(10);
// Tüketici Thread
Task.Run(() => {
foreach (var item in buffer.GetConsumingEnumerable())
{
Console.WriteLine($"Tüketiliyor: {item}");
}
});
// Üretici Thread
for (int i = 0; i < 50; i++)
{
buffer.Add(i); // Buffer doluysa burada otomatik bekler (Backpressure)
}
buffer.CompleteAdding();