Parallelität und Multithreading in C#: Ein vollständiger Leitfaden
Moderne Anwendungen erfordern hohe Leistung und Reaktionsfähigkeit. Parallelität (concurrency) und Multithreading (multithreading) in C# sind die wichtigsten Werkzeuge, um mehrere Aufgaben gleichzeitig auszuführen, Mehrkernprozessoren effizient zu nutzen und die Benutzererfahrung zu verbessern. In diesem Artikel werden wir die grundlegenden Konzepte erläutern, angefangen bei klassischen Threads bis hin zu modernem async/await und Parallel LINQ.
Grundlagen des Multithreading: Die Klasse Thread und der Thread-Pool
Multithreading in C# beginnt mit der Klasse System.Threading.Thread. Jeder Thread stellt einen separaten Ausführungspfad innerhalb eines Prozesses dar. Das manuelle Erstellen von Threads ist jedoch eine teure Operation. Zur Optimierung wird der Thread-Pool (ThreadPool) verwendet, der die Wiederverwendung von Threads verwaltet.
Beispiel für das manuelle Erstellen eines Threads:
using System;using System.Threading;
class Program{ static void Main() { Thread thread = new Thread(DoWork); thread.Start(); Console.WriteLine("Hauptthread arbeitet..."); thread.Join(); // Warten auf Beendigung }
static void DoWork() { Console.WriteLine("Thread führt Arbeit aus."); }}Für eine einfachere Verwendung wird ThreadPool empfohlen:
ThreadPool.QueueUserWorkItem(state => { Console.WriteLine("Aufgabe im Thread-Pool.");});Wichtig: Die Klasse Thread hat eine low-level API und unterstützt keine Rückgabe von Ergebnissen. Verwenden Sie für moderne Anwendungen Task.
Task und TPL (Task Parallel Library)
Die TPL-Bibliothek (System.Threading.Tasks) bietet eine high-level Abstraktion – Task. Task repräsentiert eine asynchrone Operation, die einen Wert zurückgeben kann. Dies ist die Grundlage für moderne Parallelität in C#.
Beispiel für Task ohne Rückgabewert:
using System;using System.Threading.Tasks;
class Program{ static async Task Main() { Task task = Task.Run(() => DoWork()); await task; Console.WriteLine("Abgeschlossen."); }
static void DoWork() { Console.WriteLine("Arbeit in Task."); }}Task mit Rückgabewert:
Task<int> task = Task.Run(() => { int result = 0; for (int i = 0; i < 1000000; i++) result += i; return result;});int sum = await task;Console.WriteLine($"Summe: {sum}");TPL enthält auch Parallel.For und Parallel.ForEach für die parallele Verarbeitung von Sammlungen:
Parallel.For(0, 10, i => { Console.WriteLine($"Iteration {i} auf Thread {Thread.CurrentThread.ManagedThreadId}");});Async/await: Asynchrone Programmierung
Die Schlüsselwörter async und await vereinfachen das Schreiben von asynchronem Code und machen ihn synchronem Code ähnlich. Sie erstellen keine neuen Threads, sondern wechseln effizient den Ausführungskontext und geben den Thread für andere Aufgaben frei.
Beispiel einer asynchronen Methode:
using System;using System.Net.Http;using System.Threading.Tasks;
class Program{ static async Task Main() { string data = await FetchDataAsync("https://example.com"); Console.WriteLine(data); }
static async Task<string> FetchDataAsync(string url) { using HttpClient client = new HttpClient(); string result = await client.GetStringAsync(url); return result; }}Verwendungsregeln:
- Eine Methode mit
asyncmussTask,Task<T>odervoidzurückgeben (nur für Ereignisbehandlungsroutinen).