Parallelität und Multithreading in C#: Von Thread bis async/await
Moderne Anwendungen erfordern zunehmend die gleichzeitige Ausführung mehrerer Aufgaben: von der Verarbeitung von Web-Server-Anfragen bis hin zu parallelen Berechnungen in Desktop-Programmen. In C# (.NET) gibt es dafür ein leistungsstarkes Arsenal an Werkzeugen – von klassischen Threads (Thread) bis hin zu hochwertigen Konstrukten wie async/await und der Task Parallel Library (TPL). In diesem Artikel werden wir die wichtigsten Konzepte von Parallelität und Multithreading, ihre praktische Anwendung und die besten Vorgehensweisen erläutern.
Was sind Parallelität und Multithreading?
Bevor wir zum Code übergehen, ist es wichtig, den Unterschied zwischen diesen Begriffen zu verstehen:
- Parallelität (Concurrency) – die Fähigkeit eines Programms, mehrere Aufgaben auszuführen, indem es zwischen ihnen wechselt und so die Illusion einer gleichzeitigen Ausführung erzeugt. Dies kann auch auf einem einzelnen Prozessorkern realisiert werden.
- Multithreading (Multithreading) – eine Form der Parallelität, bei der mehrere Threads (threads) auf einem Mehrkernprozessor physisch gleichzeitig ausgeführt werden.
- Parallelverarbeitung (Parallelism) – eine Teilmenge des Multithreadings, bei der verschiedene Teile einer Aufgabe strikt gleichzeitig auf verschiedenen Kernen ausgeführt werden.
In C# können Sie sowohl Threads auf niedriger Ebene als auch Abstraktionen verwenden, die Threads automatisch verwalten. Die Wahl hängt von der Aufgabe ab: Für I/O-gebundene Operationen (Arbeit mit Dateien, Netzwerk) eignet sich async/await besser, für CPU-gebundene (Berechnungen) eher Parallel.For oder Task.
Erstellen und Verwalten von Threads: Die Klasse Thread
Die Klasse System.Threading.Thread ist die niedrigste Ebene für die Arbeit mit Threads in C#. Sie bietet vollständige Kontrolle, erfordert jedoch eine sorgfältige Synchronisation.
using System;using System.Threading;
class Program{ static void Main() { Thread thread = new Thread(DoWork); thread.Start();
// Der Hauptthread arbeitet weiter for (int i = 0; i < 5; i++) { Console.WriteLine($"Hauptthread: {i}"); Thread.Sleep(100); }
thread.Join(); // Warten auf die Beendigung des Kindthreads Console.WriteLine("Arbeit abgeschlossen."); }
static void DoWork() { for (int i = 0; i < 5; i++) { Console.WriteLine($"Kindthread: {i}"); Thread.Sleep(150); } }}Probleme der manuellen Thread-Verwaltung:
- Komplexität der Synchronisation (Datenrennen, Deadlocks).
- Hoher Overhead beim Erstellen und Zerstören von Threads.
- Fehlende integrierte Unterstützung für die Rückgabe von Ergebnissen.
Daher wird in modernem C# die Task Parallel Library bevorzugt.
Task Parallel Library (TPL) und async/await
Die Klasse Task aus dem Namespace System.Threading.Tasks repräsentiert einen asynchronen Vorgang. In Kombination mit den Schlüsselwörtern async und await ermöglicht sie das Schreiben von asynchronem Code, der wie synchroner Code aussieht.
Basisbeispiel mit Task
using System;using System.Threading.Tasks;
class Program{ static async Task Main() { Console.WriteLine("Start der Arbeit.");
// Starten einer asynchronen Aufgabe Task<int> task = CalculateAsync(10);
// Während die Aufgabe läuft, können wir etwas anderes tun Console.WriteLine("Erledige andere Arbeit...");
// Warten auf das Ergebnis int result = await task; Console.WriteLine($"Ergebnis: {result}"); }
static async Task<int> CalculateAsync(int number) { await Task.Delay(1000); // Simulation einer langwierigen Operation return number * number; }}