Bir Queue<T> bir gişedeki sıra gibi çalışır: eklenen ilk öğe ilk çıkarılandır (FIFO, first in, first out). Arkaya Enqueue ile ekler, önden Dequeue ile alırsınız; ikisi de sabit zamanlıdır.
Enqueue, Dequeue ve Peek
Çıktı:
Waiting: 3
Next up: invoice.pdf
Printing invoice.pdf
Next up: photo.png
Printing photo.png
Printing report.docx
Waiting: 0
Peek, sonraki öğeyle ne yapacağınıza ona bağlanmadan önce karar vermek içindir; Dequeue bağlanır. Boşalana kadar çıkaran while (queue.Count > 0) döngüsü bir kuyruğu işlemenin standart yoludur ve döngü gövdesi yeni öğeler eklerse de çalışmaya devam eder; bir foreach buna izin vermez.
Boş kuyruk istisnası ve TryDequeue
Boş bir kuyrukta Dequeue ve Peek InvalidOperationException fırlatır. Referans tipleri için bile "boşken null" davranışı yoktur.
Çıktı:
Caught InvalidOperationException
Serving ticket 41
Serving ticket 42
False
TryDequeue ve TryPeek bir öğe olduğunda true döndürüp out değişkenini ayarlar, kuyruk boşken false döndürür. .NET Core 2.0 ile geldiler; eski .NET Framework kodunda Dequeue çağırmadan önce Count > 0 kontrol edin.
Bir kuyruğun içine bakmak
Bir kuyruk hiçbir şey çıkarmadan dolaşılabilir. foreach, ToArray ve Contains öğeleri önden arkaya görür.
Çıktı:
Ana Ben Chloe
True
Ana is first, 3 in line
3
İndeks yoktur: line[1] derlenmez ve ortadan bir öğe çıkaramazsınız. İkisinden birine ihtiyacınız varsa veri aslında bir kuyruk değildir; bir List<T> ya da bir LinkedList<T> kullanın. Diğer koleksiyonlarda olduğu gibi aynı kuyruk üzerindeki bir foreach içinde ekleme ya da çıkarma yapmak InvalidOperationException fırlatır.
Bir kuyrukla genişlik öncelikli arama
Kuyruğun sıralaması tam olarak genişlik öncelikli aramanın ihtiyaç duyduğu şeydir: önce bir adım uzaktaki her şeyi, sonra iki adım uzaktaki her şeyi ziyaret etmek ve böyle devam etmek. Burada küçük bir ağda Ana'yı diğer herkesten kaç bağlantının ayırdığını bulur:
Çıktı:
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
Her kişi, ilk ulaşıldığında bir kez kuyruğa eklenir ve kuyruk onları bulundukları sırayla geri verdiği için ilk ulaşma her zaman en kısa yol üzerindendir. Kuyruğu bir Stack ile değiştirin, aynı döngü artık en kısa yolları bulmayan derinlik öncelikli aramaya dönüşür.
Aynı şekil bir ağacın seviye sıralı dolaşmasını, bir ızgarada flood fill'i ve bağlantı taramayı halleder: kuyrukta tek bir öğeyle başlayın ve kuyruk boş olmadığı sürece birini çıkarıp ziyaret edilmemiş komşularını ekleyin.
FIFO işler için Queue ve List
Add ve RemoveAt(0) ile bir List<T>'yi kuyruk olarak kullanabilirsiniz, ama RemoveAt(0) kalan her elemanı bir yuva aşağı kaydırır. 100.000 öğelik bir listeyi böyle boşaltmak yaklaşık beş milyar eleman taşıması yapar; bir Queue<T> ise 100.000 sabit zamanlı adım yapar. İçeride bir kuyruk dairesel bir tampondur: bir dizideki baş ve kuyruk indekslerini izler ve yalnızca dolduğunda yeniden ayırır.
| İşlem | Queue<T> | Kuyruk olarak kullanılan List<T> |
|---|---|---|
| Arkaya eklemek | Enqueue, O(1) | Add, O(1) |
| Önden çıkarmak | Dequeue, O(1) | RemoveAt(0), O(n) |
| Öne bakmak | Peek | list[0] |
| İndeks erişimi | Yok | list[i] |
Birden fazla thread için ConcurrentQueue
Queue<T> thread-safe değildir. Birkaç thread öğe ekleyip aldığında System.Collections.Concurrent içindeki ConcurrentQueue<T>'yu kullanın. Enqueue, TryDequeue ve TryPeek'i vardır ama Dequeue'su yoktur, çünkü etrafta başka thread'ler varken "Count'u kontrol et sonra Dequeue yap" iki çağrı arasında başarısız olabilir.
Çıktı:
4000
4000 processed
ConcurrentQueue<T> yerine düz bir Queue<T> ile sayı zamanlamaya bağlı olarak yanlış çıkardı ya da program istisna fırlatırdı. Boşa dönmek yerine iş beklemesi gereken üretici ve tüketici thread'ler için BlockingCollection<T> (varsayılan olarak bir ConcurrentQueue<T>'yu sarar) ya da System.Threading.Channels bekleme ve tamamlanma ekler. Normal bir koleksiyonu elle korumak için lock sayfasına bakın.
PriorityQueue
Öğelerin geliş sırasına göre değil önceliğe göre çıkması gerektiğinde (önce en acil destek talebi, Dijkstra algoritmasında o ana kadarki en kısa yol), .NET 6 ve sonrası PriorityQueue<TElement, TPriority> sağlar. En düşük öncelik değeri önce çıkar:
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
Eşit öncelikli öğeler herhangi bir sırayla çıkabilir; eşitliği geliş sırası bozmalıysa önceliğe bir sıra numarası ekleyin.
Yaygın hatalar
- Kontrol etmeden Dequeue yapmak. Boş bir kuyruk
InvalidOperationExceptionfırlatır;Count > 0üzerinde döngü kurun ya daTryDequeuekullanın. Peekçağırıp öğenin gitmesini beklemek. YalnızcaDequeueçıkarır.- Kuyruk üzerindeki
foreachiçinde Enqueue yapmak. İstisna fırlatır; bunun yerine birwhiledöngüsüyle işleyin. - Bir
Queue<T>'yu thread'ler arasında paylaşmak.ConcurrentQueue<T>ya da bir lock kullanın. - Büyük veride kuyruk olarak
List.RemoveAt(0)kullanmak. Her çağrı O(n)'dir.
Sıkça Sorulan Sorular
C#'ta Queue nedir?
System.Collections.Generic içindeki Queue<T>, ilk giren ilk çıkar (FIFO) bir koleksiyondur. Enqueue arkaya bir öğe ekler, Dequeue öndeki öğeyi çıkarıp döndürür ve Peek öndeki öğeyi çıkarmadan döndürür. Üçü de sabit zaman alır.
C#'ta boş bir kuyrukta Dequeue yapınca ne olur?
Boş bir kuyrukta Dequeue ve Peek InvalidOperationException fırlatır. Önce queue.Count > 0 kontrol edin ya da istisna fırlatmak yerine false döndüren TryDequeue(out var item) ve TryPeek(out var item)'i kullanın (.NET Core 2.0'dan beri vardır).
Peek ile Dequeue arasındaki fark nedir?
Peek öndeki öğeyi döndürür ve kuyrukta bırakır, bu yüzden iki kez çağırmak aynı öğeyi döndürür. Dequeue öndeki öğeyi döndürür ve çıkarır, bu yüzden sonraki çağrı onun arkasındaki öğeyi döndürür.
C#'ta Queue<T> thread-safe mi?
Hayır. Aynı Queue<T> üzerinde aynı anda Enqueue ya da Dequeue çağıran iki thread onu bozabilir. Enqueue ve TryDequeue metotları birçok thread'den çağrılmaya güvenli olan System.Collections.Concurrent içindeki ConcurrentQueue<T>'yu kullanın ya da normal bir kuyruğa her erişimi bir lock içine alın.
List yerine neden Queue kullanılmalı?
Bir List<T>'nin ilk öğesini RemoveAt(0) ile silmek diğer her öğeyi kaydırır, bu yüzden liste büyüdükçe yavaşlar. Bir Queue<T> önden sabit zamanda siler ve API'si niyeti belirtir: öğeler geliş sırasıyla işlenir.