Dictionary<TKey, TValue> dan Queue<T> tidak dirancang untuk disentuh beberapa thread sekaligus; isi bisa ganda hitung, hilang, atau menghasilkan keadaan yang tidak valid. Namespace System.Collections.Concurrent menyediakan koleksi yang menangani situasi ini: setiap operasinya aman dipanggil dari banyak thread tanpa penguncian manual. Bab ini hanya memperkenalkan API dua koleksi yang paling sering dipakai; detail pemrograman thread dibahas pada section tersendiri.

Dua Koleksi yang Paling Sering Dipakai #

  • ConcurrentDictionary<TKey, TValue>: kamus aman thread dengan method kombinasi yang menutup celah antara cek dan aksi, seperti GetOrAdd dan AddOrUpdate.
  • ConcurrentQueue<T>: antrean FIFO aman thread; operasi keluar-masuk memakai pola Try, misalnya TryDequeue, karena isi antrean bisa berubah antar thread kapan saja.

Method kombinasi adalah kunci desainnya. Pada Dictionary<TKey, TValue> biasa, urutan if (!dict.ContainsKey(k)) lalu dict[k] = v terdiri atas dua langkah terpisah; thread lain bisa menyisipkan aksi di antaranya. GetOrAdd menjalankan cek dan aksi sebagai satu operasi atomik.

Contoh: API tanpa Thread #

Contoh berikut memakai kedua koleksi secara berurutan dalam satu thread, agar perilaku tiap method terlihat murni:

csharp
using System.Collections.Concurrent;

ConcurrentDictionary<string, int> stokGudang = new ConcurrentDictionary<string, int>();
int stokAwal = stokGudang.GetOrAdd("Kertas A4", 500);
int stokBaru = stokGudang.AddOrUpdate(
    "Kertas A4",
    0,
    (namaBarang, nilaiLama) => nilaiLama - 40);

Console.WriteLine($"Stok awal: {stokAwal}");
Console.WriteLine($"Stok setelah pengurangan: {stokBaru}");

ConcurrentQueue<string> antreanCetak = new ConcurrentQueue<string>();
antreanCetak.Enqueue("Invoice-1042.pdf");
antreanCetak.Enqueue("Laporan-Omzet.pdf");

if (antreanCetak.TryDequeue(out string? dokumen))
{
    Console.WriteLine($"Mencetak: {dokumen}");
}
Console.WriteLine($"Sisa antrean: {antreanCetak.Count}");

Output yang Dihasilkan:

text
Stok awal: 500
Stok setelah pengurangan: 460
Mencetak: Invoice-1042.pdf
Sisa antrean: 1

Mari kita bedah baris demi baris:

  1. stokGudang.GetOrAdd("Kertas A4", 500): kunci belum ada, sehingga nilai 500 ditambahkan dan dikembalikan; bila kunci sudah ada, method hanya membaca nilai lama tanpa menimpanya.
  2. stokGudang.AddOrUpdate(...): satu operasi atomik yang mengurangi stok 40; lambda menerima nama kunci dan nilai lama, lalu mengembalikan nilai baru.
  3. antreanCetak.Enqueue(...): dua dokumen masuk dengan urutan FIFO, sama seperti Queue<T> biasa.
  4. antreanCetak.TryDequeue(out string? dokumen): pola Try mengembalikan bool; pemanggil memaksa program menangani kasus antrean kosong sebagai bagian normal dari API.
  5. antreanCetak.Count: angka ini adalah perkiraan saat itu juga; pada program multithread, nilai bisa berubah tepat setelah dibaca.

Setelah semua keluarga koleksi terpetakan, dua bab penutup membahas cara menilai biayanya dan cara merancang koleksi di dalam API kita sendiri.