Einleitung: Warum braucht man Aufgabenwarteschlangen in C#?
Stellen Sie sich vor, Sie entwickeln eine Anwendung, die Bestellungen in einem Online-Shop verarbeitet. Kunden senden gleichzeitig Anfragen, und jede Anfrage benötigt Zeit (z. B. Zahlungsprüfung oder E-Mail-Versand). Wenn Sie diese nacheinander verarbeiten, wird die Anwendung langsamer und die Benutzer müssen warten. Die Lösung: Verwenden Sie eine Aufgabenwarteschlange.
Eine Aufgabenwarteschlange (Task Queue) ist eine Datenstruktur, die nach dem FIFO-Prinzip (First In, First Out – „Wer zuerst kommt, mahlt zuerst") arbeitet. In C# (.NET) sind Warteschlangen in mehreren Varianten implementiert: von der einfachen Queue<T> bis zur threadsicheren ConcurrentQueue<T>. In diesem Artikel werden wir uns ansehen, wie man Warteschlangen erstellt, Aufgaben hinzufügt und abruft und wie man sie in der asynchronen Programmierung einsetzt.
Der Artikel richtet sich an Anfänger, daher beginnen wir mit den Grundlagen und gehen schrittweise zu komplexeren Beispielen über.
1. Grundlagen der Arbeit mit Queue<T> in C#
Die Klasse Queue<T> befindet sich im Namespace System.Collections.Generic. Es handelt sich um eine einfache Warteschlange, die kein Multithreading unterstützt – sie sollte nur in Singlethread-Szenarien verwendet werden.
Erstellen einer Warteschlange und grundlegende Operationen
Erstellen wir eine Warteschlange zum Speichern von String-Aufgaben:
using System;using System.Collections.Generic;
class Program{ static void Main() { // Erstellen einer String-Warteschlange Queue<string> taskQueue = new Queue<string>();
// Aufgaben hinzufügen (Enqueue) taskQueue.Enqueue("E-Mail an Kunden senden"); taskQueue.Enqueue("Bestellstatus prüfen"); taskQueue.Enqueue("Datenbank aktualisieren");
Console.WriteLine($"Anzahl der Aufgaben: {taskQueue.Count}");
// Aufgabe abrufen (Dequeue) – entfernt und gibt das erste Element zurück string firstTask = taskQueue.Dequeue(); Console.WriteLine($"Ausführen: {firstTask}");
// Nächste Aufgabe ansehen, ohne sie zu entfernen (Peek) string nextTask = taskQueue.Peek(); Console.WriteLine($"Nächste: {nextTask}");
Console.WriteLine($"Verbleibende Aufgaben: {taskQueue.Count}"); }}Ausgabe des Programms:
Anzahl der Aufgaben: 3Ausführen: E-Mail an Kunden sendenNächste: Bestellstatus prüfenVerbleibende Aufgaben: 2Wichtige Methoden von Queue<T>
Enqueue(item)– fügt ein Element am Ende der Warteschlange hinzu.Dequeue()– entfernt und gibt das erste Element zurück. Wenn die Warteschlange leer ist, wird eineInvalidOperationExceptionausgelöst.Peek()– gibt das erste Element zurück, ohne es zu entfernen.Clear()– leert die Warteschlange.Contains(item)– prüft, ob ein Element in der Warteschlange vorhanden ist.
Wichtig: Queue<T> ist nicht threadsicher. Wenn mehrere Threads gleichzeitig Elemente hinzufügen oder entfernen, treten Fehler auf. Verwenden Sie für Multithreading-Szenarien ConcurrentQueue<T>.
2. Threadsichere Warteschlange: ConcurrentQueue<T>
In realen Anwendungen werden Aufgaben oft in verschiedenen Threads hinzugefügt und verarbeitet. Dafür gibt es in .NET die ConcurrentQueue<T> aus dem Namespace System.Collections.Concurrent.
Beispiel: Producer-Consumer (Erzeuger-Verbraucher)
Stellen Sie sich vor, ein Thread (Producer) generiert Aufgaben, während ein anderer (Consumer) sie ausführt.
using System;using System.Collections.Concurrent;using System.Threading.Tasks;
class Program{ static void Main() { ConcurrentQueue<string> queue = new ConcurrentQueue<string>();
// Producer: Fügt Aufgaben hinzu