Thread Safe – ConcurrentQueue, ConcurrentDictionary, ConcurrentBag, ConcurrentStack ve BlockingCollection Koleksiyonları ve Kullanım Durumları

Merhaba,

Yazılımsal problemlere çözüm olarak geliştirdiğimiz algoritmalar ihtiyaca dönük olarak yer yer asenkron işlevde geliştirilebilmektedirler. Asenkron süreçler, senkron gidişata nazaran process önceliğini tahmin edilemeyecek bir şekilde değiştirebilecek bir doğaya sahiptirler. Haliyle bu tahmin edilememezlik süreç esnasında olası birçok hatayla bizleri karşı karşıya bırakabilme ihtimaline sahiptirler. Dolayısıyla bizler asenkron işlemlerin ve olası hataların söz konusu olduğu bu belli başlı durumlarda temelinde locking semantiklerini benimsemiş sınıfları barındıran Thread Safe koleksiyonlarını kullanarak muhtelif önlemler almaktayız.

Herşeyden önce Thread Safe Nedir? sorusuna daha derin bir cevap vererek konuya başlayalım;
Thread Safe, anlam olarak iş parçacığı güvenliği şeklinde Türkçe ifade edebileceğimiz bir kavramdır. Thread Safe; birden çok thread’in tekbir kaynağı aynı anda kullanabilmesi/erişebilmesi durumlarında ortaya çıkabilecek tutarsızlıklar neticesinde üretilecek olası hatalara karşı o anki thread’in kaynağını güvenceye alan ve bunu o kaynağı kullanan tüm threadler için sağlayan bir konsepttir.

Yani anlayacağınız elimizdeki bir kaynağı aynı zamanda bir thread okurken bir başka thread kaynaktaki veriler üzerinde değişiklik yaparsa (yahut biri yazar bir diğeri silerse vs.) işte bu tarz asenkron durumlarda hatalar söz konusu olacaktır. İşte bu hataları engellemek ve birden fazla threadin kullandığı kaynak üzerinde oluşan değişiklikleri sadece o anda o değişikliği yapan threadin yaptığını düşündürmek için Thread Safe ile gerekli önlemler sağlanmaktadır.

Thread Safe konseptinin daha da iyi anlaşılabilmesi için “erkekler tuvaletini” düşünebilirsiniz 🙂 Nasıl ki, tuvaletteki pisuvarı o an kullanan kişi işini bitirmeden bir diğeri işine başlamıyor hah işte buradaki rajonun vermiş olduğu ahlak yazılımda Thread Safe sayesinde gerçekleştirilmektedir 🙂 Sanırım bu örnekle daha anlaşılır olmuş olsa gerek…

Bu olası hataları manüpüle edebilmek, asenkron algoritmalarımızda verisel karışıklıklarla maliyete sebep olmamak ve verimsel kesintiye uğratmamak için .NET çatısında Thread Safe koleksiyonları kullanılmaktadır. Bu koleksiyonlar System.Collections.Concurrent namespace’i altında bulunmaktadır ve ConcurrentQueue, ConcurrentDictionary, ConcurrentBag, ConcurrentStack ve BlockingCollection olmak üzere beş tanedir.

Bu koleksiyonların ortak özelliği IProducerConsumerCollection<T> arayüzünü uygulamalarıdır. Dikkat ederseniz üretici/tüketici koleksiyonları ifade eden IProducerConsumerCollection arayüzü ilgili koleksiyona yeni bir eleman eklemek(üretmek) yahut eklenmiş bir elemanı koleksiyondan çıkarmak(tüketmek) durumlarında kullanılır.

Şimdi gelin bu koleksiyonları sırasıyla inceleyelim;

  • ConcurrentQueue<T>
    Birden fazla threadin eş zamanlı bir şekilde aynı kaynak üzerinde işlem yapması durumunda, bir threadin yaptığı değişiklikten dolayı bir diğerinin patlamaması ve birbirlerini ezmemesi için ConcurrentQueue koleksiyonu kullanılabilir.Örneğin aşağıdaki senaryoyla durumu canlandırabiliriz;

    Üç masalı bir lokantada yoğunluktan dolayı yer problemi yaşanmaktadır. Masalarda yer boşaldıkça sıradaki müşteri boşalan yere alınmakta ve bu işlem eş zamanlı olarak tüm masalar için gerçekleştirilmektedir.

    Bu senaryo için aşağıdaki geliştirmeyi yaptığımızı düşünürsek;

        class Program
        {
            static Queue siradakiler;
            static void Main(string[] args)
            {
                siradakiler = new Queue();
                siradakiler.Enqueue("Ahmet");
                siradakiler.Enqueue("Ali");
                siradakiler.Enqueue("Mehmet");
                siradakiler.Enqueue("Necati");
                siradakiler.Enqueue("Ayşe");
                siradakiler.Enqueue("Mahmut");
                siradakiler.Enqueue("Hilmi");
                siradakiler.Enqueue("Hüseyin");
                siradakiler.Enqueue("Rıfkı Abi");
    
                Kontrol().Wait();
            }
    
            static async Task Masa(string masaAdi)
            {
                while (siradakiler.Count &gt; 0)
                {
                    await Task.Delay(100);
                    Console.WriteLine($"{masaAdi} - {siradakiler.Dequeue()}");
                }
            }
    
            static async Task Kontrol()
            {
                var task1 = Masa("Masa 1");
                var task2 = Masa("Masa 2");
                var task3 = Masa("Masa 3");
    
                await Task.WhenAll(task1, task2, task3);
                Console.WriteLine("Masa sırası bitmiştir.");
            }
        }
    

    Dikkat ederseniz, müşteri sırasını Queue koleksiyonu ile tutmaktayız. Dolayısıyla bu şekilde eş zamanlı bir çalışmanın olduğu bir senaryoda Queue koleksiyonunu kullanmak olası hatalara kapı aralamakta ve kaynak üzerinde bir thread tarafından değişiklik söz konusu olduğunda diğer thread kaynağının değiştiğine dair özellikle aşağıdaki hatayı fırlatmaktadır;

    System.AggregateException: ‘One or more errors occurred. (Queue empty.)’

    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları
    Keza bu değişiklik “Kontrol” fonksiyonu içerisinde asenkron bir şekilde çalıştırılan farklı tasklar aracılığıyla gerçekleştirilmektedir.

    İşte böyle bir senaryoda Queue koleksiyonu yerine ConcurrentQueue koleksiyonunu kullanabiliriz. ConcurrentQueue koleksiyonu; threadler arasında soyutlamayı gerçekleştirmekte ve kaynaktaki tüm elemanların sade ve sadece bir kereye mahsus bir task tarafından işlenmesine müsaade etmektedir.

        class Program
        {
            static ConcurrentQueue&lt;string&gt; siradakiler;
            static void Main(string[] args)
            {
                siradakiler = new ConcurrentQueue&lt;string&gt;();
                siradakiler.Enqueue("Ahmet");
                siradakiler.Enqueue("Ali");
                siradakiler.Enqueue("Mehmet");
                siradakiler.Enqueue("Necati");
                siradakiler.Enqueue("Ayşe");
                siradakiler.Enqueue("Mahmut");
                siradakiler.Enqueue("Hilmi");
                siradakiler.Enqueue("Hüseyin");
                siradakiler.Enqueue("Rıfkı Abi");
    
                Kontrol().Wait();
            }
        
            static async Task Masa(string masaAdi)
            {
                while (siradakiler.Count &gt; 0)
                {
                    await Task.Delay(100);
                    siradakiler.TryDequeue(out string siradaki);
                    Console.WriteLine($"{masaAdi} - {siradaki}");
                }
            }
    
            static async Task Kontrol()
            {
                var task1 = Masa("Masa 1");
                var task2 = Masa("Masa 2");
                var task3 = Masa("Masa 3");
    
                await Task.WhenAll(task1, task2, task3);
                Console.WriteLine("Masa sırası bitmiştir.");
            }
        }
    

    Yukarıdaki kod bloğunu incelerseniz eğer Queue yerine kullanılan ConcurrentQueue koleksiyonu “TryDequeue” fonksiyonu ile operasyonu gerçekleştirmekte ve tüm threadleri çakıştırmaksızın süreci başarıyla sonlandırabilmektedir.
    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları

  • ConcurrentDictionary<TKey, TValue>
    İhtiyacımız doğrultusunda üretilecek key – value konseptindeki ikili verileri bir asenkron operasyonla Dictionary koleksiyonunda toplama durumlarında süreçte mükerrer tanımlanmaya zorlanabilecek key değerlerine istinaden karşılaşabileceğimiz olası hatalara karşı ConcurrentDictionary koleksiyonunu tercih edebiliriz.Bu koleksiyonu aşağıdaki senaryoyla örneklendirebiliriz;

    TSK bünyesinde 100 kişilik bir grubu dağılım dengesini gözetmeksizin rastgele bir şekilde iki bölüğe ayırmak istiyoruz.

    Burada her bir kişi sayısal olarak key değeriyle temsil edilecekken, karşılığınada bölük bilgisi value olarak atanacaktır. Şimdi bu işlemi aşağıda gerçekleştirelim;

        class Program
        {
            static Dictionary<int, string> askerler;
            static void Main(string[] args)
            {
                askerler = new Dictionary<int, string>();
                AtamaGerceklestir().Wait();
                foreach (KeyValuePair<int, string> item in askerler)
                    Console.WriteLine($"{item.Key} - {item.Value}");
                Console.Read();
            }
    
            static async Task Ata(string bolukAdi)
            {
                for (int i = 1; i <= 100; i++)
                {
                    await Task.Delay(100);
                    if (!askerler.ContainsKey(i))
                        askerler.TryAdd(i, bolukAdi);
                }
            }
    
            static async Task AtamaGerceklestir()
            {
                var task1 = Ata("1. Bölük");
                var task2 = Ata("2. Bölük");
    
                await Task.WhenAll(task1, task2);
            }
        }
    

    Bu şekilde bir inşa neticesinde aşağıdaki hatayla karşılaşmak muhtemeldir.

    One or more errors occurred. (Operations that change non-concurrent collections must have exclusive access. A concurrent update was performed on this collection and corrupted its state. The collection’s state is no longer correct.)

    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları
    Bu olası hatanın sebebi, asenkron operasyon esnasında farklı threadlar tarafından ilgili Dictionary içerisinde mükerrer key değerlerinin üretilmeye çalışılmasıdır. Üretilen ilgili key değerinin varlığına dair 18. satırda alınan önlem o anki thread için eklemeye izin vermektedir. Dolayısıyla aynı anda aynı key değeri üretilmekte ve Dictionary koleksiyonuna eklenmekte/eklenmeye çalışmakta ve haklı olarak patlama yaşanmaktadır.

    İşte böyle bir durumda, süreçte asenkron işlenen herhangi bir threade o anki key değerinin sade ve sadece kendisi tarafından üretildiğini bildirecek ve diğer threadlerin ezmesine müsade etmeyecek olan thread safe fıtratındaki ConcurrentDictionary koleksiyonunu kullanmamız problemi çözecek ve bizim yerimize gerekli kontrollerle organizasyonu sağlayacaktır.

    İlgili çalışmayı aşağıdaki gibi ConcurrentDictionary koleksiyonu ile tekrar tazelersek;

        class Program
        {
            static ConcurrentDictionary<int, string> askerler;
            static void Main(string[] args)
            {
                askerler = new ConcurrentDictionary<int, string>();
                AtamaGerceklestir().Wait();
                foreach (KeyValuePair<int, string> item in askerler)
                    Console.WriteLine($"{item.Key} - {item.Value}");
                Console.Read();
            }
    
            static async Task Ata(string bolukAdi)
            {
                for (int i = 1; i <= 100; i++)
                {
                    await Task.Delay(100);
                    if (!askerler.ContainsKey(i))
                        askerler.TryAdd(i, bolukAdi);
                }
            }
    
            static async Task AtamaGerceklestir()
            {
                var task1 = Ata("1. Bölük");
                var task2 = Ata("2. Bölük");
    
                await Task.WhenAll(task1, task2);
            }
        }
    

    Projeyi derleyip çalıştırdığımızda benzersiz key değerlerinin oluşturulduğunu ve karşılığına gerekli value değerlerinin basıldığını göreceksiniz.
    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları

  • ConcurrentBag<T>
    Elimizdeki veri kümesinde bulunan elemanları işlerken sıralamayı gözönüne almadığımız tüm işlemlerde bu koleksiyon tercih edilebilir. Asenkron senaryolarda bu koleksiyon üzerinde çalışan tüm threadler için esasında ilgili koleksiyondan bir model oluşturulmakta ve her bir iş parçacığı kendisine ait model üzerinde çalıştığından dolayı herhangi bir çakışma söz konusu olmamaktadır.ConcurrentBag koleksiyonundan her eleman alındığında Stack’te olduğu gibi listeye eklenen en sonuncu eleman elde edilecektir. Lakin burada vurgulanması gereken nokta şudur ki; her bir thread için eklenen en son eleman kendisinde eklenen son elemandır. Dolayısıyla n elemanlı bir koleksiyonda threadlerden birinde hiç bir eleman eklenmedi ve en sonuncu eleman elde edilmeye çalışıldıysa, işte böyle bir durumda diğer threadler tarafından son eleman olarak eklenenlerden birini rastgele alacak ve bizlere geri döndürecektir. İşte böyle bir durumda ConcurrentBag koleksiyonunun kullanıldığı threadler arasında çekişme söz konusu olabilecektir.

    Bilakis ConcurrentBag koleksiyonuna yeni bir eleman eklenmek istendiği vakit sıkıntısız süreç işlenecek ve neticede threadlar arası herhangi bir çakışma söz konusu olmayacağından dolayı sağlıklı bir şekilde ekleme işlemi gerçekleştirilecektir. Sadece Queue koleksiyonuna karşı bu işlem biraz daha yavaş gerçekleştirilecektir. Ama diğer bir yandan Queue ve Stack koleksiyonlarında da farklı threadler üzerinden eleman eklemek söz konusu olmadığından dolayı bu yavaşlık göze alınabilecek bir maliyet olarak değerlendirilebilmektedir.

    Velhasıl… ConcurrentBag koleksiyonu, ekleme ve okuma işlemleri için threadler üzerinde eşit bir dağılım söz konusuysa eğer çok faydalı olacaktır. Ayrıca yukarıdaki satırlarda değindiğimiz gibi eleman elde etmek istendiği vakit ya ilgili thread tarafından en son eklenen eleman ya da diğer threadlerden herhangi birinin eklediği en sonucusu elimize ulaşacaktır. İşte bu mantıktan dolayı o anda hangi elemanın işlendiğinin önemli olmadığı senaryolarda gönül rahatlığıyla kullanılabilecek bir koleksiyondur.

            static ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
            static void Main(string[] args)
            {
                AutoResetEvent autoResetEvent = new AutoResetEvent(false);
                concurrentBag.Add(0);
                #region Task 1
                Task.Run(() =>
                {
                    Console.WriteLine("****");
                    for (int i = 1; i < 10; i++) concurrentBag.Add(i); autoResetEvent.Set(); }); #endregion #region Task 2 Task.Run(() =>
                  {
                      Console.WriteLine("----");
                      while (!concurrentBag.IsEmpty)
                          if (concurrentBag.TryTake(out int data))
                              Console.WriteLine(data);
                      autoResetEvent.WaitOne();
                  });
                #endregion
    
                Console.Read();
            }
    

    Yukarıdaki örnek kod bloğunu incelerseniz eğer; iki farklı taskta farklı threadlar devreye sokulmuş ve Task 1’de ConcurrentBag koleksiyonu doldurulurken Task 2’de ise ilgili koleksiyondaki değerler elde edilip ekrana yazdırılmıştır. 5. satırda koleksiyonumuza direkt olarak Main fonksiyonu içerisinden bir eleman atanmıştır. Nihayetinde bu atama işleminin Main’de olması demek Task 1’e nazaran farklı threadde olması demektir. Şimdi bu kodu derleyip çalıştırdığımızda nasıl bir sonuçla karşılaşıyoruz inceleyelim ve üzerine mantığı hep beraber istişare edelim.

    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları

    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları

    Yukarıdaki şemada belirtildiği gibi ilk olarak Task 1 devreye girse dahi süreç asenkron olduğu için her iki yapıda birbirinden bağımsız eş zamanlı bir şekilde sürece dahil olmakta ve işlenmektedir. Burada ilk etapta “0” değerinin yazmasını beklerken Task 1 işlevi neticesinde koleksiyona dahil edilecek tüm değerlerin yazıldığını görmekteyiz. Bunun nedeni “AutoResetEvent” sınıfının “WaitOne” metodu sayesinde Task 2 faaliyeti sona erdirilmeden asenkron süreçteki diğer fonksiyonlar(Task 1) devam ettirilmekte ve nihayetinde Task 1 içerisinde yine aynı sınıfın “Set” metodu ile bekletilen thread sürece devam ettirilmektedir. Haliyle koleksiyon içerisindeki değerler değişse dahi Thread Safe koleksiyon olmasından dolayı kaynak revize edilse dahi bir hata meydana vermemekte ve yeni verileride while faaliyeti bir sonraki değeri itere ederek elde edip çıktı olarak vermektedir. Ayrıca “0” değerinin en sonda elde edilmesi ise yukarıdaki satırlarda açıklamaya çalıştığımız, her bir threadin kendi eklediğini en son olarak nitelemesi ve getirmesi olayının açık bir tezahürüdür. Neticede ilgili “0” değerini Task 1 değil Main thread’i eklemiş bulunmaktadır.

    Bir başka örnek vermek gerekirse, sıradan koleksiyon kullanılan operasyonlarda kaynak değişmesi durumunda alınacak olası hataları ConcurrentBag ile rahatlıkla aşabildiğimiz üzerine verelim.

            static List<int> sayilar = new List<int>();
            static void Main(string[] args)
            {
                Task.Run(() =>
                  {
                      for (int i = 1; i <= 10; i++)
                          sayilar.Add(i);
                      int sayac = 0;
                      while (sayac < sayilar.Count) Console.WriteLine($"{sayilar[sayac++]}"); }); Task.Run(() =>
                {
                    for (int i = 11; i <= 20; i++)
                        sayilar.Add(i);
                });
    
                Console.Read();
            }
    

    Yukarıdaki asenkron çalışma neticesinde her iki threadin kullandığı kaynak olan List koleksiyonu değişikliğe uğradığı esnada aşağıdaki olası hatayı meydana getirebilir.


    System.ArgumentOutOfRangeException: ‘capacity was less than the current size.
    Parameter name: value’

    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları

    Bu olası hatalara mahal vermemek için ConcurrentBag kullanılabilir;

            static ConcurrentBag<int> concurrentBag = new ConcurrentBag<int>();
            static void Main(string[] args)
            {
                Task.Run(() =>
                {
                    for (int i = 1; i <= 10; i++) concurrentBag.Add(i); while (concurrentBag.IsEmpty == false) if (concurrentBag.TryTake(out int data)) Console.WriteLine($"{data}"); }); Task.Run(() =>
                {
                    for (int i = 11; i <= 20; i++)
                        concurrentBag.Add(i);
                });
    
                Console.Read();
            }
    

    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları

  • ConcurrentStack<T>
    Aynı kaynağı kullanan en az iki threadın söz konusu olduğu durumlarda bir thread ile kaynağa veri eklerken bir diğeriyle silme işlemlerinin gerçekleştiği senaryolarda ConcurrentStack koleksiyonu kullanılabilir.

            static ConcurrentStack<int> concurrentStack = new ConcurrentStack<int>();
            static AutoResetEvent autoResetEvent1 = new AutoResetEvent(false);
            static AutoResetEvent autoResetEvent2 = new AutoResetEvent(false);
            static void Main(string[] args)
            {
                //Ekle
                Task.Run(() =>
                {
                    for (int i = 1; i <= 10; i++) { concurrentStack.Push(i); autoResetEvent2.Set(); autoResetEvent1.WaitOne(); } autoResetEvent2.Set(); }); //Çıkar Task.Run(() =>
                {
                    while (concurrentStack.IsEmpty)
                    {
                        autoResetEvent2.WaitOne();
                        if (concurrentStack.TryPop(out int item))
                        {
                            Console.WriteLine(item);
                            autoResetEvent1.Set();
                        }
                    }
                });
    
                Console.Read();
            }
    

    Yukarıdaki kod bloğunu incelerseniz eğer; iki adet tanımlanan “AutoResetEvent” nesnesi aracılığıyla her iki thread’de ki akış senkronize edilmekte ve bu yönlendirme ile ilgili koleksiyon içerisine hem eleman eklenmekte hem de çıkarılmaktadır.
    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları

  • BlockingCollection<T>
    Veri kaynağının boş olduğu durumlarda eleman talep edildiği vakit bir eleman ekleninceye kadar metot akışını bekleten ve başka bir thread tarafından ilgili kaynağa eleman eklenince bu elemanı alarak geriye döndüren bir koleksiyondur. Ayriyetten bunların yanında belirlediğimiz kotayı aşamaması için boyutu limitlenebilen bir koleksiyondur. BlockingCollection koleksiyonu farklı overloadlarında yukarıda ele aldığımız ConcurrentQueue, ConcurrentStack ve ConcurrentBag koleksiyonlarıyla beraber çalışmaktadır.

    Şöyle ki;

    • BlockingCollection<T>; bu constructer kullanılıyorsa eğer arka planda bir ConcurrentQueue koleksiyonu oluşturulmaktadır ve o noktadan itibaren ilgili koleksiyon mantığında çalışmasına devam edecektir.
    • BlockingCollection<T>(Int32); bu constructer ise arka planda yukarıdaki gibi bir ConcurrentQueue koleksiyonu oluşturmaktadır lakin aldığı int parametresi sayesinde koleksiyonun boyutunu sınırlandıracaktır. Eğer ki, koleksiyonun limiti sınıra dayanmış ise süreçteki herhangi bir yeni eleman ekleme durumunda ilgili koleksiyon yer boşalana kadar bekleyecek ve ilk fırsatta eklemeyi gerçekleştirecektir.
    • BlockingCollection<T>(IProducerConsumerCollection<T>); ilgili koleksiyonu bir ConcurrentStack yahut ConcurrentBag mantığında değerlendirecektir.
    • BlockingCollection<T>(IProducerConsumerCollection<T>, Int32); bu constructer ise ilgili koleksiyonu ConcurrentStack yahut ConcurrentBag mantığında çalıştıracak ve bir yandan da boyut eşiğini belirlemiş olacaktır.

    Bu bilgiler eşliğinde aşağıdaki kod bloğunu incelersek eğer;

            static BlockingCollection<int> blockingCollection = new BlockingCollection<int>();
            static void Main(string[] args)
            {
                int sayac = 10;
                Task.Run(() =>
                {
                    while (sayac >= 1) blockingCollection.Add(sayac--);
                });
    
                Task.Run(() =>
                {
                    while (sayac != 1) Console.WriteLine(blockingCollection.Take());
                });
    
                Console.Read();
            }
    

    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları
    Eğer ilk devreye 2. task girerse koleksiyondaki değerleri okumaya çalışacak lakin ilk etapta koleksiyon içerisinde eleman olmayacağından dolayı eklenmesini bekleyecek ve böylece 1. task devreye girdiğinde gerekli veriler koleksiyona set edilmiş olacaktır. Netice olarak 2. task koleksiyon içerisindeki elemanları tek tek elde edebilecektir.

    Son olarak yukarıdaki kod bloğunda bir hususa dikkat çekersek eğer tanımlanan tasklar global sayaç değişkeniyle döngüleri kontrol etmektedirler. Dolayısıyla bu şekilde bizler BlockingCollection kütüphanesinin yeni veri eklenmesine dair ihtiyacını hissetmemekteyiz. Eğer ki, BlockingCollection koleksiyonuna eklenecek olan verinin sonuna gelindiğine dair bilgi vermek istiyorsak ekleme işleminden sonra CompleteAdding metodunu çağırmanız yeterlidir. Bu metot çağrıldıktan sonraki ilk Take talebinde InvalidOperationException hatası fırlatılacak ve böylece yeni bir veri girilmesine dair beklenti sona erecektir.

            static BlockingCollection<int> blockingCollection = new BlockingCollection<int>();
            static void Main(string[] args)
            {
                int sayac = 10;
                Task.Run(() =>
                {
                    while (sayac >= 1) blockingCollection.Add(sayac--);
                    blockingCollection.CompleteAdding();
                });
    
                Task.Run(() =>
                {
                    try
                    {
                        while (true) Console.WriteLine(blockingCollection.Take());
                    }
                    catch (InvalidOperationException ex)
                    {
                        Console.WriteLine(ex.Message);
                    }
                });
    
                Console.Read();
            }
    

    Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları

Netice olarak son bir değerlendirmeye mazhar olmamız gerekirse eğer incelediğimiz bu Thread Safe koleksiyonları performans açısından normal koleksiyonlara nazaran oldukça düşük seviyede çalışmakta ve maliyeti arttırmaktadırlar. Bu koleksiyonları asenkron operasyonlar dahilinde yukarıda değindiğimiz senaryolara uygun bir şekilde değerlendirmenizi ve yersiz bir şekilde kullanımından kaçınmanızı önemle vurguluyorum.

Thread Safe - ConcurrentQueue, ConcurrentDictionary, ConcurrentBag ve ConcurrentStack Koleksiyonları ve Kullanım Durumları

Thread Safe – Eşzamanlı Koleksiyonların performans ölçüsü

İlgilenenlerin faydalanması dileğiyle…
Sonraki yazılarımda görüşmek üzere…
İyi çalışmalar…

Bunlar da hoşunuza gidebilir...

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir