CodeGym /Kurse /C# SELF /Einführung in Multithreading

Einführung in Multithreading

C# SELF
Level 55, Lektion 0
Verfügbar

1. Einführung

Multithreading ist wie die parallele Arbeit mehrerer Kollegen im Büro: einer druckt Dokumente, ein anderer ruft einen Kunden an, der dritte kocht Kaffee (und natürlich sind das alles Programmierer). Wenn wir nur einen Mitarbeiter hätten, würde er alles nacheinander machen, und das Büro würde vor Langeweile und Kaffeeschlangen ersticken. In der Programmierung ist die Situation ähnlich: ein single-threaded Programm kann jeweils nur eine Aufgabe ausführen.

Stell dir vor, unsere Anwendung führt eine langwierige Operation aus, zum Beispiel lädt eine Datei aus dem Internet oder berechnet eine riesige Tabelle. Alles andere ist in diesem Moment "eingefroren" — Buttons reagieren nicht, Animationen stoppen, die Worte "nicht antwortet" erscheinen im Popup.

Multithreading ermöglicht es der Anwendung, mehrere Dinge gleichzeitig zu tun: die UI bleibt responsiv, Operationen laufen parallel und wir müssen nicht in einen Monolog verfallen wie "Computer, du hängst schon wieder?!".

Grundbegriffe und Terminologie

Bevor wir eintauchen, klären wir, was ein Thread ist und worin er sich von einem Prozess unterscheidet.

  • Prozess (Process): Ein eigenständiges Programm mit eigenem Adressraum, Variablen und Ressourcen. Zum Beispiel ist jede gestartete Anwendung in Windows ein eigener Prozess.
  • Thread (Thread): Eine Ausführungseinheit innerhalb eines Prozesses. Ein Prozess kann einen oder mehrere Threads enthalten, die dieselben Ressourcen (Speicher, Variablen) teilen.

Warum wirft Multithreading so viele Fragen auf?

Weil Threads wilde Typen sind: sie können jederzeit mit der Ausführung beginnen, Daten durcheinanderbringen, sich gegenseitig unterbrechen und im Speicher Chaos anrichten, wenn man nicht auf die Reihenfolge achtet. Wenn es sich anfühlt wie ein Kindergarten ohne Aufsicht — genau so ist es! Disziplin und Sorgfalt im Umgang mit Threads sind die Grundlage für zuverlässige Multithread-Programme.

2. Geschichte und Rolle von Multithreading in C# und .NET

Früher waren C#-Programme oft single-threaded und einfach. Mit steigenden Performance-Anforderungen, dem Aufkommen von Multicore-CPUs und dem Bedarf an responsiven Anwendungen ohne UI-Hänger kamen in .NET Mittel für Multithreading hinzu. Zuerst gab es die klassische System.Threading.Thread-Klasse, später erschienen Tasks (Task), async-Methoden (async/await), parallele Datenverarbeitung (PLINQ) und höherstufige Synchronisationsprimitive.

C# ist zu einer mächtigen Plattform herangewachsen, wo Multithreading keine Seltenheit, sondern Alltag ist.

Visuell: Prozess und Threads

Hier eine einfache Schema:


+--------------------------------------------------+
|                 Prozess (dein Programm)          |
|      +-------------+   +-------------+           |
|      |    Thread 1 |   |   Thread 2  |           |
|      +-------------+   +-------------+           |
|                ...                                 |
|      +-------------+                              |
|      |    Thread N |                              |
|      +-------------+                              |
+--------------------------------------------------+

Alle Threads innerhalb eines Prozesses sehen gemeinsame Variablen und Ressourcen.

3. Wie erstellt man einen Thread in C#?

Fangen wir mit dem einfachsten an: der Thread-Klasse aus dem Namespace System.Threading.

Beispiel: Einen zweiten Thread starten

Nehmen wir an, wir haben eine lang laufende Aufgabe — z.B. die Summe der Zahlen von 1 bis 10_000_000. Während gerechnet wird, soll der Hauptthread eine Begrüßung ausgeben.


using System;
using System.Threading;

class Program
{
    // Methode für die zweite Aufgabe
    static void CalculateSum()
    {
        long sum = 0;
        for (int i = 1; i <= 10_000_000; i++)
            sum += i;
        Console.WriteLine($"[Thread 2] Summe: {sum}");
    }

    static void Main()
    {
        // Wir erstellen einen Thread und geben das Delegat an die Methode
        Thread thread = new Thread(CalculateSum);
        
        thread.Start(); // Wir starten den zweiten Thread

        // Der Hauptthread macht weiter
        Console.WriteLine("[Thread 1] Hallo! Wir arbeiten parallel...");

        // Wir warten auf das Ende des zweiten Threads vor dem Beenden
        thread.Join();

        Console.WriteLine("[Thread 1] Alles fertig!");
    }
}

Was passiert?
Auf dem Bildschirm erscheint die Zeile "[Thread 1] Hallo! Wir arbeiten parallel...", und dann, wenn der Rechen-Thread fertig ist, wird die Summe ausgegeben.

Typisches Problem: Wer zuerst schreibt, gewinnt

Probiere, den Code mehrmals auszuführen — die Reihenfolge der Ausgaben kann unterschiedlich sein! Manchmal erscheint die Summe zuerst, manchmal die Begrüßung. Das ist echte Multithreading-Wirkung — Programme werden weniger deterministisch, wie die Laune einer Katze am Montag.

4. Speicherbereich: was sehen Threads?

Alle Threads innerhalb eines Prozesses haben Zugriff auf dieselben Variablen (sofern sie nicht lokal für eine Methode sind). Wenn eine Variable in einem Thread geändert wird, sehen die anderen Threads die Änderung!

Beispiel: Gemeinsame Variable


using System;
using System.Threading;

class Program
{
    static int counter = 0;

    static void Increment()
    {
        for (int i = 0; i < 1000; i++)
            counter++;
    }

    static void Main()
    {
        Thread t1 = new Thread(Increment);
        Thread t2 = new Thread(Increment);

        t1.Start();
        t2.Start();

        t1.Join();
        t2.Join();

        Console.WriteLine($"counter = {counter}");
    }
}

Welche Zahl erwarten wir für counter? Logisch wäre 2000, weil jeder Thread 1000 mal erhöht.

Aber nein! Starte das Programm mehrmals — du wirst unterschiedliche Werte sehen: 1782, 1935, 1999…
Warum? Das ist das klassische Problem der Race Condition — Threads "fangen" einander zwischen Lesen -> Erhöhen -> Schreiben, und einige Inkremente gehen verloren.

5. Nützliche Feinheiten

Wie interagieren Threads mit der UI?

In modernen Desktop-Anwendungen (WinForms/WPF/MAUI) bedient der Hauptthread die GUI. Alle Benutzeraktionen (Klicks, Eingaben) laufen in diesem Thread. Hintergrundaufgaben sollten in anderen Threads ausgeführt werden, aber per Regel darf man die UI nicht direkt aus anderen Threads "anfassen". Das verhindert Chaos.

In der Konsole gibt es diese Einschränkung nicht, man kann aus jedem Thread in Console.WriteLine schreiben. Aber in echten Anwendungen kann ohne richtige Synchronisation die UI "aus dem Tritt geraten".

Sequenz und Parallelität

Eine Tabelle, um die Unterschiede zu festigen.

Single-threaded Code Multithreaded Code
Führt Aufgaben nacheinander aus Aufgaben können gleichzeitig ausgeführt werden
UI "hängt" bei langen Aufgaben UI bleibt responsiv
Variablen einfach lesen und schreiben Erfordert Kontrolle über den Datenzugriff
Einfach zu debuggen Kann schwierig zu debuggen sein

Wichtige Punkte beim Arbeiten mit Threads

  • Gemeinsame Variablen — gemeinsames Risiko. Wie oben gezeigt, wenn mehrere Threads eine gemeinsame Variable nutzen — ohne Synchronisation können Fehler auftreten! (Mehr dazu in den nächsten Vorlesungen.)
  • Auf einen Thread kann man mit Join() warten. Die Methode Join() erlaubt es, den Hauptthread bis zum Abschluss des Hintergrundthreads zu blockieren. Nutze sie, wenn du auf ein Ergebnis warten musst.
  • Einen Thread kann man nicht zweimal starten. Nach der Ausführung kann ein Thread nicht erneut gestartet werden — du musst ein neues Thread-Objekt erstellen.
  • Beendigung eines Threads. Ein Thread endet, wenn seine Methode fertig ausgeführt ist. Threads gewaltsam zu "killen" ist keine gute Idee (die Methode Abort() gilt seit langem als schädlich und veraltet).

Wozu braucht man Multithreading in der Praxis?

  • UI-Anwendungen: die Oberfläche nicht einfrieren lassen, wenn Hintergrund-Downloads oder Berechnungen laufen.
  • Server und Dienste: gleichzeitig viele Client-Anfragen bearbeiten.
  • Hochleistungsberechnungen: eine große Aufgabe (z.B. Verarbeitung von Millionen von Datensätzen) in Teile zerlegen und parallel ausführen.
  • Games, Simulationen, Datenverarbeitung: komplexe Systeme modellieren, ohne Performance zu verlieren.

Probleme des Multithreadings

Multithreading bringt Power, aber auch Komplexität:

  • Race Condition (Wettlaufszustand): wenn mehrere Threads gleichzeitig dieselben Daten ändern, hängt das Ergebnis von der Reihenfolge der Anweisungen ab, die unvorhersehbar ist.
  • Deadlock (gegenseitige Sperre): Threads warten aufeinander, und niemand kann weitermachen.
  • Starvation (Verhungern): ein Thread wird ständig vom Zugriff auf eine Ressource ausgeschlossen.

In dieser Vorlesung haben wir nur die Hauptprobleme von Multithreading erwähnt; in den nächsten lernst du, sie zu erkennen und zu vermeiden. Merke dir vorerst: wenn alles "hängt", können nicht nur Bugs Schuld sein, sondern auch Threads, die sich eine Party veranstaltet haben.

2
Aufgabe
C# SELF, Level 55, Lektion 0
Gesperrt
Erstellung und Start eines Threads
Erstellung und Start eines Threads
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION