Menu

Queue di C#: Enqueue, Dequeue, Peek dan TryDequeue

Queue<T> adalah collection first in, first out: item keluar sesuai urutan kedatangannya. Pelajari Enqueue, Dequeue, dan Peek, exception saat queue kosong dan TryDequeue, breadth first search dengan queue, serta kapan memakai ConcurrentQueue atau PriorityQueue.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

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.

OperasiQueue<T>List<T> yang dipakai sebagai queue
Tambah di belakangEnqueue, O(1)Add, O(1)
Hapus dari depanDequeue, O(1)RemoveAt(0), O(n)
Lihat item depanPeeklist[0]
Akses lewat indeksTidak tersedialist[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 dengan Count > 0 atau pakai TryDequeue.
  • Memanggil Peek dan mengira itemnya sudah hilang. Hanya Dequeue yang menghapus.
  • Melakukan enqueue di dalam foreach pada queue itu. Ini melempar exception; proses dengan perulangan while.
  • Memakai satu Queue<T> bersama di beberapa thread. Pakai ConcurrentQueue<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.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai