Arbeiten mit Aufgabenwarteschlangen in C#: Ein praktischer Leitfaden

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum braucht man Aufgabenwarteschlangen in C#?

\n

In der modernen Entwicklung mit C# (.NET) entsteht häufig die Notwendigkeit, asynchrone Datenverarbeitung zu organisieren. Ein klassisches Beispiel ist ein Webserver, der Anfragen annimmt und diese im Hintergrund verarbeiten muss, ohne den Hauptthread zu blockieren. Oder eine Anwendung, die Dateien aus dem Internet herunterlädt und parallel bereits heruntergeladene Dateien verarbeitet. Für solche Szenarien eignet sich das Producer-Consumer-Muster (Erzeuger-Verbraucher) ideal, und seine Grundlage ist eine Aufgabenwarteschlange.

\n

Eine Aufgabenwarteschlange ermöglicht es, Arbeit sicher von einem Thread (oder einer asynchronen Operation) zu einem anderen zu übertragen und dabei den Zugriff auf gemeinsame Daten zu synchronisieren. In diesem Artikel betrachten wir drei grundlegende Methoden zur Implementierung von Warteschlangen in C#: ConcurrentQueue, BlockingCollection und das moderne System.Threading.Channels. Jede davon eignet sich für unterschiedliche Szenarien, und wir werden sie anhand praktischer Beispiele erläutern.

\n\n

1. ConcurrentQueue — Einfache threadsichere Warteschlange

\n

ConcurrentQueue<T> ist die einfachste threadsichere Warteschlange aus dem Namensraum System.Collections.Concurrent. Sie verwendet lock-freie Algorithmen (Compare-and-Swap, CAS) für hohe Leistung. Allerdings blockiert sie den Thread nicht, wenn die Warteschlange leer ist – Sie müssen das Warten selbst implementieren.

\n\n

Beispiel: Daten aus mehreren Quellen laden

\n

Angenommen, wir haben die Aufgabe: 10 Webseiten herunterladen und verarbeiten. Ein Thread (Producer) fügt URLs zur Warteschlange hinzu, und mehrere Threads (Consumers) holen sie ab und laden sie herunter.

\n
using System;\nusing System.Collections.Concurrent;\nusing System.Threading.Tasks;\n\npublic class ConcurrentQueueExample\n{\n    public static async Task RunAsync()\n    {\n        var queue = new ConcurrentQueue<string>();\n        var urls = new[] { "https://example.com", "https://dotnet.microsoft.com" };\n\n        // Producer: Aufgaben hinzufügen\n        foreach (var url in urls)\n        {\n            queue.Enqueue(url);\n        }\n\n        // Consumers: Aufgaben verarbeiten\n        var tasks = new Task[3];\n        for (int i = 0; i < tasks.Length; i++)\n        {\n            tasks[i] = Task.Run(() =>\n            {\n                while (queue.TryDequeue(out var url))\n                {\n                    Console.WriteLine($"Verarbeitung: {url}");\n                    // Arbeit simulieren\n                    Task.Delay(500).Wait();\n                }\n            });\n        }\n\n        await Task.WhenAll(tasks);\n        Console.WriteLine("Alle Aufgaben erledigt.");\n    }\n}
\n

Vorteile: Einfachheit, hohe Leistung bei vielen Operationen.
\nNachteile: Kein eingebauter Wartemechanismus (Schleife mit Überprüfung erforderlich), schwierig, die Warteschlangengröße zu begrenzen.

\n\n

2. BlockingCollection — Warteschlange mit Blockierung und Begrenzung

\n

BlockingCollection<T> ist ein Wrapper um eine threadsichere Sammlung, der die Möglichkeit hinzufügt, den Consumer-Thread zu blockieren, wenn die Warteschlange leer ist, und den Producer-Thread, wenn die Warteschlange voll ist. Standardmäßig wird ConcurrentQueue verwendet, aber jede Sammlung, die IProducerConsumerCollection<T> implementiert, kann eingesetzt werden.

\n\n

Beispiel: Auftragsverarbeitung mit Puffergrenze

\n

Stellen Sie sich ein Auftragsverarbeitungssystem vor. Der Producer generiert Aufträge, und drei Consumer verarbeiten sie. Der Puffer ist auf 10 Elemente begrenzt, um nicht zu viel Speicher zu verbrauchen.

\n
using System;\nusing System.Collections.Concurrent;\nusing System.Threading.Tasks;\n\npublic class BlockingCollectionExample\n{\n    public static async Task RunAsync()\n    {\n        using var orders = new BlockingCollection<string>(boundedCapacity: 10);\n\n        // Producer\n        var producer = Task.Run(() =>\n        {\n            for (int i = 1; i <= 20; i++)\n                

Blogs

Buchempfehlungen