CodeGym /Kurslar /C# SELF /ConcurrentBag və ConcurrentDictionary

ConcurrentBag və ConcurrentDictionary

C# SELF
Səviyyə, Dərs
Mövcuddur

1. Giriş

ConcurrentBag<T> — bu, thread-safe, sıralı olmayan kolleksiyadır. Onun əsas xüsusiyyəti və üstünlüyü "Bag" (torba) sözü ilə bağlıdır: çıxarış zamanı elementlərin sırası zəmanət edilmir. Bu o deməkdir ki, əlavə etmə sırasına əsasən gözlədiyiniz element çıxmaya bilər. Amma ConcurrentBag müəyyən ssenarilərdə onu çox sürətli edən unikal optimizasiya malikdir.

ConcurrentBag xüsusiyyətləri

Sıralamanın olmaması: Queue-lardan (FIFO) və Stack-lərdən (LIFO) fərqli olaraq, ConcurrentBag TryTake()-ın sizə elementi əlavə olunma sırasına görə konkret bir qaydada qaytaracağını vəd etmir. Bu əsas fərqdir.

Lokallığa optimizasiya (Thread-Local Storage): ConcurrentBag-ın əsas mövcudluq səbəbi — bir elementi əlavə edən thread-in çox vaxt onu eyni thread-in çıxarması ehtimalının yüksək olduğu ssenarilərdə performansıdır.

Nümunə: ConcurrentBag — əlavə və çıxarış

using System.Collections.Concurrent;

ConcurrentBag<string> itemBag = new ConcurrentBag<string>();

// Əlavə etmə
itemBag.Add("Punkt A");
itemBag.Add("Punkt B");
itemBag.Add("Punkt V");

Console.WriteLine($"Torbadakı elementlər: {itemBag.Count}"); // Çıxış: Torbadakı elementlər: 3

// Elementləri çıxarma (sıra zəmanət edilmir!)
if (itemBag.TryTake(out string item1))
{
    Console.WriteLine($"Çıxarıldı: {item1}"); // Ola bilər "Punkt V", "Punkt B" və ya "Punkt A"
}

if (itemBag.TryTake(out string item2))
{
    Console.WriteLine($"Çıxarıldı: {item2}");
}

Console.WriteLine($"Qalan elementlər: {itemBag.Count}"); // Çıxış: Qalan elementlər: 1

Bu kodu bir neçə dəfə işə salanda çıxarılan elementlərin sırasının dəyişə biləcəyini görəcəksiniz.

Add(), TryTake() metodları

Add(T item): elementi ConcurrentBag-ə əlavə etmək üçün istifadə olunur. Əməliyyat thread-safe-dir.

TryTake(out T item): ConcurrentBag-dən elementi çıxarmağa cəhd edir. Əgər element uğurla çıxarılıbsa true, boşdursa false qaytarır. Vacibdir ki, TryTake thread-i bloklamır.

2. İstifadə ssenariləri

ConcurrentBag ConcurrentQueue və ya ConcurrentStack-ın əvəzçisi deyil. O, spesifik hallarda parlayır:

Obyekt/resurs hovuzları: əgər sizin təkrar istifadə olunacaq obyekt hovuzunuz varsa və obyektin qaytaran thread-in çox vaxt həmin obyekti yenidən götürməsi arzu olunursa. Bu ümumi resursa qarşı rəqabəti azaldır.

TPL-də dinamik task bölüşdürməsi: Parallel.ForEach və Parallel.For kimi konstruktsiyaların daxili işi lokal torbalar və "work-stealing" mexanizmini istifadə edərək işi effektiv paylayır.

ConcurrentBag və lokal optimizasiya ilə task hovuzu

using System.Collections.Concurrent;
using System.Threading.Tasks;
using System.Threading;

ConcurrentBag<string> taskPool = new ConcurrentBag<string>();

// Hovuzu ilkin tapşırıqlarla doldururuq
for (int i = 0; i < 10; i++)
{
    taskPool.Add($"Vəzifə {i}");
}

void Worker()
{
    // Hər thread tapşırıq götürməyə çalışır
    while (taskPool.TryTake(out string task))
    {
        Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: Emal edir {task}");
        Thread.Sleep(50); // İşin simulyasiyası
    }
    Console.WriteLine($"Thread {Thread.CurrentThread.ManagedThreadId}: İşini bitirdi.");
}

// Bir neçə worker thread işə salırıq
// Task.Run(Worker);
// Task.Run(Worker);
// Task.Run(Worker);
// Thread.Sleep(1000); // İşə vaxt veririk

Bu nümunədə ConcurrentBag thread-lərin tapşırıqları effektiv götürməsinə imkan verir, daxili strukturu sayəsində låck-ları minimallaşdırır.

Daxili mexanizm

ConcurrentBag yüksək performansa thread-local storage (TLS) istifadə etməklə nail olur. Thread elementi əlavə etdikdə, o, həmin thread-ə lokal struktura qoyulur. TryTake() əvvəlcə lokal strukturu yoxlayır; boşdursa — digər thread-lərdən və ya qlobal hovuzdan "work-stealing" həyata keçirilir. Bu rəqabəti azaldır və lokal erişim vacib olan hallarda kolleksiyanı əla seçim edir.

3. Thread-safe dictionary

ConcurrentDictionary<TKey, TValue> — ən çox istifadə olunan thread-safe kolleksiyalardan biridir: bir neçə thread-dən təhlükəsiz əlavə, oxuma, yeniləmə və silmə əməliyyatları üçün yüksək performanslı dictionary.

Adi Dictionary<TKey, TValue> heç də thread-safe deyil. Hər hansı yazma (əlavə/dəyişmə/silmə) və ya yazma zamanı oxuma istisnalara (InvalidOperationException) və ya məlumatların korlanmasına gətirə bilər.

Nümunə: adi Dictionary-də problem (təkrarlama)

using System.Collections.Generic;
using System.Threading.Tasks;

Dictionary<int, int> concurrentDictProblem = new Dictionary<int, int>();

void AddToDict(int start, int count)
{
    for (int i = 0; i < count; i++)
    {
        // Eyni zamanda əlavə/yeniləmə cəhdi
        // İstisnalara və ya səhv davranışa səbəb olacaq
        concurrentDictProblem[start + i] = start + i;
    }
}

//Main-də nümunəni işə salmaq:
try
{
    Task t1 = Task.Run(() => AddToDict(0, 10000));
    Task t2 = Task.Run(() => AddToDict(5000, 10000)); // Açarların üst-üstə düşməsi
    Task.WaitAll(t1, t2);
    Console.WriteLine($"Problematik dictionary-də elementlər: {concurrentDictProblem.Count}");
}
catch (Exception ex)
{
    Console.WriteLine($"Adi dictionary-də xəta: {ex.Message}");
}

Bu kod demək olar ki, qəti olaraq istisna verəcək və ya thread-safe olmama səbəbindən donacaq.

4. Əsas əməliyyatlar

ConcurrentDictionary "check + act" tipli ixtisaslaşmış atomik əməliyyatlar təqdim edir.

TryAdd(TKey key, TValue value): açar-dəyər cütünü atomik şəkildə əlavə edir. Əgər açar əlavə olunubsa true, artıq mövcuddursa false qaytarır.

ConcurrentDictionary<string, int> scores = new ConcurrentDictionary<string, int>();
if (scores.TryAdd("Alice", 100))
    Console.WriteLine("Alice əlavə olundu."); // Çıxış: Alice əlavə olundu.
if (!scores.TryAdd("Alice", 150))
    Console.WriteLine("Alice artıq var."); // Çıxış: Alice artıq var.

TryGetValue(TKey key, out TValue value): açara görə dəyəri atomik şəkildə əldə edir.

if (scores.TryGetValue("Alice", out int aliceScore))
    Console.WriteLine($"Alice balı: {aliceScore}"); // Çıxış: Alice balı: 100

TryUpdate(TKey key, TValue newValue, TValue comparisonValue): cari dəyər comparisonValue-ə bərabərdirsə atomik şəkildə yeniləyir. Yarışları qarşısını alır.

// Cari dəyər Alice = 100
if (scores.TryUpdate("Alice", 120, 100)) // 100-u 120-ə yeniləyəcək
    Console.WriteLine("Alice balı 120-yə yeniləndi."); // Çıxış: Alice balı 120-yə yeniləndi.
if (!scores.TryUpdate("Alice", 130, 100)) // Yeniləməyəcək, çünki cari 120-dir, 100 deyil
    Console.WriteLine("Alice balı yenilənmədi (köhnə məlumat)."); // Çıxış: ...

TryRemove(TKey key, out TValue value): açara görə elementi atomik şəkildə silir.

if (scores.TryRemove("Alice", out int removedScore))
    Console.WriteLine($"Alice silindi, balı idi: {removedScore}"); // Çıxış: Alice silindi, balı idi: 120

5. İrəli səviyyəli atomik əməliyyatlar

Bu iki metod ConcurrentDictionary-nin əsas iş atlarını təşkil edir və bir çox ssenarini əhatə edir.

GetOrAdd(TKey key, TValue valueFactory(TKey key)): açara görə mövcud dəyəri atomik şəkildə qaytarır, yoxdursa factory ilə yaradıb əlavə edir. Cache-lər və unikal entitetlər üçün idealdır.

// Tutaq ki, ağır obyektləri cache-ləyirik
ConcurrentDictionary<int, HeavyObject> objectCache = new ConcurrentDictionary<int, HeavyObject>();

HeavyObject GetOrCreateHeavyObject(int id)
{
    // Əgər varsa — qaytarır, yoxsa yaradır və əlavə edir
    return objectCache.GetOrAdd(id, (key) =>
    {
        Console.WriteLine($"ID üçün yeni HeavyObject yaradıram: {key}");
        return new HeavyObject(key); // Bahalı obyektin yaradılmasının simulyasiyası
    });
}

// Main-də:
HeavyObject obj1 = GetOrCreateHeavyObject(1); // Yeni yaradır
HeavyObject obj2 = GetOrCreateHeavyObject(2); // Yeni yaradır
HeavyObject obj3 = GetOrCreateHeavyObject(1); // Mövcud obj1-i qaytarır

AddOrUpdate(TKey key, TValue addValue, Func<TKey, TValue, TValue> updateValueFactory): açar yoxdursa atomik şəkildə əlavə edir, varsa update-factory ilə yeniləyir.

  • addValue: açar tapılmadıqda əlavə olunacaq dəyər.
  • updateValueFactory: açara və cari dəyərə əsaslanaraq yeni dəyəri hesablayan funksiya.
// Səhifə baxışlarının sayılması
ConcurrentDictionary<string, int> pageViews = new ConcurrentDictionary<string, int>();

void IncrementPageView(string page)
{
    pageViews.AddOrUpdate(page, 1, // Yeni səhifədirsə 1 əlavə et
                          (key, existingVal) => existingVal + 1); // Əks halda 1 artır
    Console.WriteLine($"Səhifə '{page}' {pageViews[page]} dəfə ziyarət edilib.");
}

// Main-də:
IncrementPageView("Home");   // Home: 1
IncrementPageView("About");  // About: 1
IncrementPageView("Home");   // Home: 2
IncrementPageView("Home");   // Home: 3
IncrementPageView("Contact"); // Contact: 1

6. Cache və state idarəetməsi üçün istifadə nümunələri

Data caching: ConcurrentDictionary in-memory cache üçün əla seçimdir: GetOrAdd bahalı obyektlərin təkrar yaranmasının qarşısını alır.

İstifadəçi session-lərinin idarəsi: müxtəlif sorğulardan gələn məlumatların təhlükəsiz saxlanması və yenilənməsi.

Statistika sayımı: AddOrUpdate ilə event, baxış, səs sayğaclarını inkrement etmək rahatdır.

Reyestrlər/Service Locator: müxtəlif thread-lərdən əlçatan olan qeydiyyatdan keçmiş servis və plugin-lərin saxlanması.

ConcurrentDictionary<TKey, TValue> yüksək optimizasiya olunmuş kolleksiyadır və manual synchronizasiya tələb etmədən dictionary-lərlə multi-thread inkişafını xeyli sadələşdirir.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
`ConcurrentDictionary` istifadə edərək statistik məlumatların atomik hesablanması
`ConcurrentDictionary` istifadə edərək statistik məlumatların atomik hesablanması
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION