Parallelität und Multithreading in C#: Fortgeschrittenes Niveau

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

Parallelität und Multithreading in C#: Fortgeschrittenes Niveau

\n\n

In der modernen Entwicklung mit C# (.NET) unterscheidet die Fähigkeit, Parallelität und Multithreading effektiv zu verwalten, einen erfahrenen Ingenieur von einem Anfänger. Grundkenntnisse über Thread und einfache Sperren reichen nicht mehr aus, um leistungsstarke, reaktionsfähige und skalierbare Anwendungen zu erstellen. In diesem Artikel behandeln wir fortgeschrittene Techniken: vom tiefen Verständnis von async/await bis zur Nutzung von System.Threading.Channels und TPL Dataflow. Das Material richtet sich an Entwickler, die bereits mit den Grundlagen des Multithreading vertraut sind.

\n\n

1. Tiefer Einstieg in async/await und SynchronizationContext

\n\n

Viele Entwickler verwenden async/await, verstehen aber nicht vollständig, wie der Task Scheduler und SynchronizationContext die Fortsetzungen (continuations) steuern. Auf fortgeschrittenem Niveau ist es wichtig zu kontrollieren, in welchem Thread der Code nach await ausgeführt wird.

\n\n

Standardmäßig erfasst await den aktuellen SynchronizationContext (oder TaskScheduler). In UI-Anwendungen (WPF, WinForms) wird die Ausführung dadurch in den Hauptthread zurückgeführt. In ASP.NET Core und Konsolenanwendungen fehlt der Kontext oft, und die Fortsetzung kann in jedem Thread des Pools ausgeführt werden.

\n\n

Für die Feineinstellung verwenden Sie ConfigureAwait(false). Dies teilt der Laufzeitumgebung mit, nicht zu versuchen, in den ursprünglichen Kontext zurückzukehren, was die Leistung verbessert und Deadlocks in Bibliothekscode verhindert.

\n\n
public async Task<string> FetchDataAsync(HttpClient client)\n{\n    // Die Fortsetzung wird den Kontext nicht erfassen\n    var result = await client.GetStringAsync("https://api.example.com")\n                              .ConfigureAwait(false);\n    // Dieser Code wird in einem beliebigen Thread des Pools ausgeführt\n    return ProcessResult(result);\n}\n\npublic async Task<string> FetchDataWithContextAsync(HttpClient client)\n{\n    // Die Fortsetzung kehrt in den ursprünglichen Kontext zurück (z.B. UI-Thread)\n    var result = await client.GetStringAsync("https://api.example.com");\n    // Dieser Code wird garantiert im ursprünglichen Kontext ausgeführt\n    return ProcessResult(result);\n}
\n\n

Fortgeschrittener Tipp: Erstellen Sie eigene SynchronizationContext-Implementierungen für nicht standardmäßige Szenarien, z.B. zur Organisation einer Aufgabenwarteschlange mit einer festen Anzahl von Threads.

\n\n

2. Task Parallel Library (TPL) und Datenparallelität

\n\n

Die TPL bietet hochwertige Abstraktionen für Parallelverarbeitung. Parallel.For und Parallel.ForEach teilen Sammlungen automatisch in Teile (Chunks) auf und verteilen sie auf Threads. Auf fortgeschrittenem Niveau muss man jedoch verstehen, wie man den Parallelitätsgrad und die Ausnahmebehandlung konfiguriert.

\n\n

Verwenden Sie ParallelOptions, um die Anzahl der Threads zu begrenzen, sowie ParallelLoopState für einen vorzeitigen Ausstieg aus der Schleife.

\n\n
var options = new ParallelOptions\n{\n    MaxDegreeOfParallelism = Environment.ProcessorCount / 2 // Nutze die Hälfte der Kerne\n};\n\nParallel.ForEach(sourceData, options, (item, state) =>\n{\n    try\n    {\n        // Schwere Operation\n        ProcessItem(item);\n    }\n    catch (Exception ex)\n    {\n        // Protokollieren und Ausführung abbrechen\n        Console.WriteLine($"Fehler: {ex.Message}");\n        state.Stop(); // Bricht die Schleifenausführung ab\n    }\n});
\n\n

Wichtig: Parallel.ForEach garantiert keine Verarbeitungsreihenfolge. Wenn die Reihenfolge wichtig ist, verwenden Sie Parallel.ForEach mit einem Index oder PLINQ (AsParallel().AsOrdered()).

\n\n

3. Kanäle (System.Threading.Channels) für Producer-Consumer

\n\n

Die Klasse Channel<T> wurde in .NET Core 3.0 eingeführt und ist ein leistungsstarkes Werkzeug für Producer-Consumer-Szenarien. Sie ist threadsicher, asynchron und effizient. Kanäle gibt es

Blogs

Buchempfehlungen