Queue<T> bekerja seperti antrean di loket: item yang pertama ditambahkan adalah yang pertama diambil (FIFO, first in, first out). Kamu menambahkan di belakang dengan Enqueue dan mengambil dari depan dengan Dequeue, keduanya dalam waktu konstan.
Enqueue, Dequeue, dan Peek
Output:
Waiting: 3
Next up: invoice.pdf
Printing invoice.pdf
Next up: photo.png
Printing photo.png
Printing report.docx
Waiting: 0
Peek dipakai untuk memutuskan apa yang akan dilakukan dengan item berikutnya sebelum benar-benar mengambilnya; Dequeue benar-benar mengambilnya. Perulangan while (queue.Count > 0) yang terus melakukan dequeue sampai kosong adalah cara standar memproses queue, dan cara ini tetap berjalan jika isi perulangan menambahkan item baru ke queue, sesuatu yang tidak diizinkan oleh foreach.
Exception saat queue kosong dan TryDequeue
Dequeue dan Peek pada queue kosong melempar InvalidOperationException. Tidak ada perilaku "null jika kosong", bahkan untuk reference type.
Output:
Caught InvalidOperationException
Serving ticket 41
Serving ticket 42
False
TryDequeue dan TryPeek mengembalikan true dan mengisi variabel out jika ada item, dan mengembalikan false jika queue kosong. Keduanya hadir di .NET Core 2.0; di kode .NET Framework yang lebih lama, periksa Count > 0 sebelum memanggil Dequeue.
Melihat isi queue
Kamu bisa melakukan enumerasi pada queue tanpa menghapus apa pun. foreach, ToArray, dan Contains semuanya melihat item dari depan ke belakang.
Output:
Ana Ben Chloe
True
Ana is first, 3 in line
3
Queue tidak punya indeks: line[1] tidak bisa dikompilasi, dan kamu tidak bisa menghapus item dari tengah. Jika kamu butuh salah satunya, datamu sebenarnya bukan queue; pakai List<T> atau LinkedList<T>. Sama seperti collection lain, melakukan enqueue atau dequeue di dalam foreach pada queue yang sama akan melempar InvalidOperationException.
Breadth first search dengan queue
Urutan queue persis seperti yang dibutuhkan breadth first search: kunjungi semua yang berjarak satu langkah, lalu semua yang berjarak dua langkah, dan seterusnya. Di sini BFS mencari berapa koneksi yang memisahkan Ana dari semua orang lain di sebuah jaringan kecil:
Output:
Ana: 0 step(s) from Ana
Ben: 1 step(s) from Ana
Chloe: 1 step(s) from Ana
Dev: 2 step(s) from Ana
Eli: 2 step(s) from Ana
Fay: 3 step(s) from Ana
Setiap orang dimasukkan ke queue sekali saja, yaitu saat pertama kali dicapai, dan karena queue mengembalikan mereka sesuai urutan ditemukannya, pertama kali itu selalu lewat jalur terpendek. Ganti queue dengan Stack dan perulangan yang sama menjadi depth first search, yang tidak lagi menemukan jalur terpendek.
Bentuk yang sama bisa dipakai untuk traversal level order pada tree, flood fill pada grid, dan menjelajahi link: mulai dengan satu item di queue, lalu selama queue tidak kosong, ambil satu dan masukkan tetangganya yang belum dikunjungi.
Queue vs List untuk pekerjaan FIFO
Kamu bisa memakai List<T> sebagai queue dengan Add dan RemoveAt(0), tetapi RemoveAt(0) menggeser setiap elemen yang tersisa satu posisi ke bawah. Mengosongkan list berisi 100.000 item dengan cara itu butuh sekitar lima miliar perpindahan elemen; Queue<T> hanya butuh 100.000 langkah berwaktu konstan. Di balik layar, queue adalah circular buffer: ia mencatat indeks head dan tail ke dalam sebuah array dan hanya mengalokasikan ulang saat penuh.
| Operasi | Queue<T> | List<T> yang dipakai sebagai queue |
|---|---|---|
| Tambah di belakang | Enqueue, O(1) | Add, O(1) |
| Hapus dari depan | Dequeue, O(1) | RemoveAt(0), O(n) |
| Lihat item depan | Peek | list[0] |
| Akses lewat indeks | Tidak tersedia | list[i] |
ConcurrentQueue untuk banyak thread
Queue<T> tidak thread safe. Jika beberapa thread menambah atau mengambil item, pakai ConcurrentQueue<T> dari System.Collections.Concurrent. Class ini punya Enqueue, TryDequeue, dan TryPeek, tetapi tidak punya Dequeue, karena saat ada thread lain, langkah "periksa Count lalu Dequeue" bisa gagal di antara kedua panggilan itu.
Output:
4000
4000 processed
Jika ConcurrentQueue<T> diganti dengan Queue<T> biasa, hitungannya bisa salah atau program melempar exception, tergantung waktunya. Untuk thread producer dan consumer yang seharusnya menunggu pekerjaan alih-alih terus berputar, BlockingCollection<T> (yang secara default membungkus ConcurrentQueue<T>) atau System.Threading.Channels menambahkan fitur blocking dan penyelesaian. Lihat lock untuk melindungi collection biasa secara manual.
PriorityQueue
Jika item harus keluar berdasarkan prioritas, bukan urutan kedatangan (tiket paling mendesak lebih dulu, jalur terpendek sejauh ini di algoritma Dijkstra), .NET 6 dan sesudahnya menyediakan PriorityQueue<TElement, TPriority>. Nilai prioritas terendah keluar lebih dulu:
var triage = new PriorityQueue<string, int>();
triage.Enqueue("sprained ankle", 3);
triage.Enqueue("chest pain", 1);
triage.Enqueue("headache", 5);
while (triage.TryDequeue(out string patient, out int priority))
{
Console.WriteLine($"{priority}: {patient}");
}
// 1: chest pain
// 3: sprained ankle
// 5: headache
Item dengan prioritas yang sama bisa keluar dalam urutan apa pun; tambahkan nomor urut ke prioritasnya jika urutan kedatangan harus menjadi penentu saat nilainya sama.
Kesalahan umum
- Melakukan dequeue tanpa memeriksa. Queue kosong melempar
InvalidOperationException; pakai perulangan denganCount > 0atau pakaiTryDequeue. - Memanggil
Peekdan mengira itemnya sudah hilang. HanyaDequeueyang menghapus. - Melakukan enqueue di dalam
foreachpada queue itu. Ini melempar exception; proses dengan perulanganwhile. - Memakai satu
Queue<T>bersama di beberapa thread. PakaiConcurrentQueue<T>atau lock. - Memakai
List.RemoveAt(0)sebagai queue untuk data besar. Setiap panggilan butuh waktu O(n).
Pertanyaan yang sering diajukan
Apa itu Queue di C#?
Queue<T> di System.Collections.Generic adalah collection first in, first out (FIFO). Enqueue menambahkan item di belakang, Dequeue menghapus dan mengembalikan item di depan, dan Peek mengembalikan item di depan tanpa menghapusnya. Ketiganya berjalan dalam waktu konstan.
Apa yang terjadi jika Dequeue dipanggil pada queue kosong di C#?
Dequeue dan Peek pada queue kosong melempar InvalidOperationException. Periksa queue.Count > 0 lebih dulu, atau pakai TryDequeue(out var item) dan TryPeek(out var item), yang mengembalikan false alih-alih melempar exception (tersedia sejak .NET Core 2.0).
Apa perbedaan Peek dan Dequeue?
Peek mengembalikan item di depan dan membiarkannya tetap di queue, jadi memanggilnya dua kali mengembalikan item yang sama. Dequeue mengembalikan item di depan lalu menghapusnya, jadi panggilan berikutnya mengembalikan item di belakangnya.
Apakah Queue<T> thread safe di C#?
Tidak. Dua thread yang memanggil Enqueue atau Dequeue pada Queue<T> yang sama secara bersamaan bisa merusaknya. Pakai ConcurrentQueue<T> dari System.Collections.Concurrent, yang Enqueue dan TryDequeue-nya aman dipanggil dari banyak thread, atau bungkus setiap akses ke queue biasa dengan lock.
Kenapa memakai Queue, bukan List?
Menghapus item pertama dari List<T> dengan RemoveAt(0) menggeser semua item lainnya, jadi makin lambat seiring list makin besar. Queue<T> menghapus dari depan dalam waktu konstan, dan API-nya menunjukkan maksudnya dengan jelas: item diproses sesuai urutan kedatangan.