CodeGym /Kurse /C# SELF /Asynchronität vs. Multithreading (

Asynchronität vs. Multithreading ( async/await und Thread)

C# SELF
Level 59 , Lektion 1
Verfügbar

1. Einführung

In der Programmierung verwechseln Anfänger (und sogar erfahrene Entwickler) oft zwei ähnliche, aber tatsächlich verschiedene Konzepte: Multithreading und Asynchronität. Bei Vorstellungsgesprächen wird diese Frage gern gestellt, um zu sehen, ob die Person den Unterschied versteht — denn davon hängt direkt ab, wie man schnellen und reaktiven Code schreibt.

Lassen Sie uns klären, worin der Trick besteht.

Multithreading: wenn viele Hände arbeiten

Multithreading ist die Organisation der Programmarbeit mithilfe mehrerer Threads. Ein Thread ist ein Ausführungsfaden, eine eigene "Spur", auf der die CPU die Anweisungen des Programms abarbeitet. Ein Prozess (z. B. unsere .NET-Anwendung) kann mehrere Threads starten, damit unterschiedliche Aufgaben gleichzeitig ausgeführt werden.

Alltagsbeispiel: Du bist Projektmanager und delegierst verschiedene Aufgaben an verschiedene Kollegen — und all diese Aufgaben werden gleichzeitig erledigt. Zum Beispiel schreibt einer den Bericht, ein anderer ruft den Kunden an, der Dritte macht die Präsentation.

Kernidee von Multithreading: Aufgaben laufen wirklich parallel (oder quasi-parallel, wenn nur eine CPU vorhanden ist, durch schnelles Kontextwechseln).

Asynchronität: die Fähigkeit, nicht untätig zu bleiben

Asynchronität ist die Organisation des Codes so, dass das Programm etwas anderes tun kann, während es auf den Abschluss einer langwierigen Operation wartet (z. B. eine Antwort aus dem Internet oder das Lesen einer Datei). Asynchroner Code verwendet nicht zwingend mehrere Threads! Er blockiert einfach die Ausführung Ihres Programms nicht während der Wartezeit einer Operation.

Alltagsbeispiel: Anstatt an der Kaffeemaschine zu stehen und zuzusehen, wie der Kaffee tropft, gibst du der Maschine den Auftrag und beschäftigst dich mit etwas anderem (beantwortest E-Mails, liest Nachrichten), und holst deinen Kaffee, wenn er fertig ist.

Kernidee der Asynchronität: nicht untätig sein, während man auf "lange" Aufgaben wartet, sondern etwas Nützliches tun.

2. Worin besteht der Unterschied?

Sehr oft werden Asynchronität und Multithreading zusammen verwendet, und dadurch wächst die Verwirrung. Aber tatsächlich beantworten diese Techniken unterschiedliche Fragen:

  • Multithreading wird benötigt, um Arbeit wirklich zu parallelisieren, indem verfügbare Prozessoren/Kerne genutzt werden.
  • Asynchronität wird benötigt, um das Warten auf Ressourcen (netzwerkbasierte I/O, disk I/O usw.) sinnvoll zu organisieren, ohne einen Thread zu blockieren.

Man kann sie kombinieren, aber sie müssen nicht miteinander verbunden sein.

Kurzvisualisierung

Multithreading (Threads) Asynchronität (Async)
Wann anwenden? Wenn die Aufgabe die CPU "frisst" (CPU-bound: Berechnungen, Rendering, Array-Verarbeitung) Wenn die Aufgabe auf ein Ereignis "wartet" (I/O-bound: Netzwerk, Festplatte, Datenbank)
Was macht der Code? Er belastet die CPU, startet mehrere Threads, parallelisiert wirklich Wenn auf Daten gewartet wird ("Leerlauf"), gibt er den Thread frei — dieser kann etwas anderes tun
Worauf wird gesteuert? Die Anzahl gleichzeitig ausführender Threads Wer gerade beschäftigt/frei ist und was zu tun ist, wenn die Operation fertig ist
Typisches Beispiel Video-Kompression, Bild-Rendering Datei-Download, HTTP-Request an einen Server

Beispiel 1: Multithreading — schnell rechnen

Angenommen, wir haben eine schwere Aufgabe: die Summe großer Zahlen berechnen.


void ComputeSum(long start, long end)
{
    long sum = 0;
    for (long i = start; i <= end; i++)
    {
        sum += i;
    }
    Console.WriteLine($"Summe von {start} bis {end} = {sum}");
}

// Starten wir drei Tasks gleichzeitig — jeder berechnet seinen Teil
Thread t1 = new Thread(() => ComputeSum(1, 1000_000_000));
Thread t2 = new Thread(() => ComputeSum(1000_000_001, 2000_000_000));
Thread t3 = new Thread(() => ComputeSum(2000_000_001, 3000_000_000));

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

// Warten wir auf das Ende aller Threads
t1.Join();
t2.Join();
t3.Join();

Warum hier Threads?
Weil die Arbeit CPU-lastig ist. Threads belasten die CPU wirklich, und wenn du eine Mehrkern-Maschine hast — wird die Arbeit schneller.

Beispiel 2: Asynchronität — wir warten auf die Serverantwort

Netzwerk ist langsam, und während wir auf die Antwort vom Server warten, kann der Thread "frei" sein.


// Laden einer Webseite asynchron, der Thread wird nicht blockiert
async Task DownloadPageAsync()
{
    using HttpClient client = new HttpClient();
    string html = await client.GetStringAsync("https://dotnet.microsoft.com/");
    Console.WriteLine(html.Length);
}

Warum hier Asynchronität?
Wir geben dem System die Anweisung "Starte den Download", und blockieren dabei nicht den Thread, sondern warten auf eine Benachrichtigung, wenn die Daten eintreffen.

3. Asynchronität ohne Multithreading: Mythos oder Wahrheit?

Frage: Ist asynchroner Code immer das Starten eines neuen Threads?
Antwort: Nein! Oft erfordert Asynchronität überhaupt keine zusätzlichen Threads.

Wenn du zum Beispiel await file.ReadAsync(...) aufrufst, startet .NET die Operation asynchron auf OS-Ebene, und der aufrufende Thread wird sofort "frei" und kehrt in den Threadpool zurück. Wenn die Operation abgeschlossen ist, findet sich ein freier Thread im Pool, der die Ausführung deiner Aufgabe fortsetzt.

  • Würdest du statt Asynchronität das synchrone (file.Read(...)) verwenden — würde der Thread einfach auf den Abschluss der Operation warten und nichts tun.
  • Asynchroner Code sagt: "Prozessor, solange wir warten — beschäftige dich mit etwas anderem!"

Wichtige Veranschaulichung:


// Der "Thread" wird nicht blockiert, er wartet nur, bis die Operation fertig ist
await Task.Delay(1000); // Einfach eine Sekunde warten — verbraucht keine CPU!

Multithreading ohne Asynchronität

Es gibt Fälle, in denen es wirklich Sinn macht, die Arbeit nur mit Threads zu parallelisieren: schwere Berechnungen, große Schleifen zur Datenverarbeitung usw. In solchen Fällen bringt Asynchronität nichts für die Beschleunigung der Berechnungen, weil die CPU sowieso zu 100% ausgelastet ist.

Klassiсher Fall: Verarbeitung großer Dateien


// Dieser Code belastet die CPU wirklich — Asynchronität hilft nicht.
void CalculateHash(string file)
{
    byte[] data = File.ReadAllBytes(file); // synchron!
    // Berechne den Hash...
}

Willst du schneller werden — starte mehrere Threads, jeder arbeitet an seiner Datei.

4. Wie sieht das in deiner Anwendung aus?

Asynchrone Operationen (await)

In unserer Übungsanwendung kann man asynchrones Laden von Daten hinzufügen. Zum Beispiel, wenn du Wechselkurse oder das Wetter abfragst — ist es besser, das asynchron zu tun.


async Task GetWeatherAsync(string city)
{
    using HttpClient client = new HttpClient();
    string json = await client.GetStringAsync($"https://api.weather.com/{city}");
    // Weiterarbeiten, wenn die Antwort eingegangen ist
    Console.WriteLine($"Wetter in {city}: {json}");
}

Was passiert unter der Haube?

Der await-Aufruf "schneidet" deine Methode in zwei Teile:

  • Die asynchrone Operation wurde gestartet — der OS-Thread wird "freigegeben" und kann andere Aufgaben erledigen.
  • Wenn die Daten eintreffen — wird die Ausführung der Methode auf einem der freien Threads aus dem Pool fortgesetzt.

Multithreading für komplexe Berechnungen

In unserem Beispiel mit dem Taschenrechner (angenommen, er verarbeitet nun große Arrays) — hier macht es Sinn, Berechnungen in separaten Threads laufen zu lassen.


// Teilen wir die große Aufgabe in kleinere Teile, jeder berechnet seinen Teil
List<Thread> threads = new List<Thread>();
for (int i = 0; i < 4; i++)
{
    int rangeStart = i * 1000000;
    int rangeEnd = (i + 1) * 1000000 - 1;
    Thread t = new Thread(() => ComputeSum(rangeStart, rangeEnd));
    threads.Add(t);
    t.Start();
}
// Warten auf das Ende aller Threads
foreach (Thread t in threads) t.Join();

5. Typische Fehler und Feinheiten bei der Arbeit mit Asynchronität

Fehler Nr. 1: async "für Geschwindigkeit" verwenden.
Ein sehr verbreiteter Irrtum ist zu denken, dass async die Ausführung des Codes beschleunigt. Das ist nicht so. Asynchronität geht um Reaktionsfähigkeit, nicht um Geschwindigkeit.

Wenn die Aufgabe CPU-bound ist (CPU-Last) — macht Asynchronität sie nicht schneller.
Wenn die Aufgabe I/O-bound ist (Netzwerk, Festplatte) — ist Asynchronität nützlich, weil der Thread nicht untätig herumsteht und sich mit anderer Arbeit beschäftigen kann.

Fehler Nr. 2: Threads durch Wait() und Result blockieren.
In asynchronem Code darf man Wait() oder die Eigenschaft Result von Tasks nicht aufrufen. Das führt fast immer zu Thread-Blocking und Deadlocks.


// Schlecht! Blockiert den Thread und verursacht Probleme
var result = GetDataAsync().Result;

async Task<string> GetDataAsync() { /* ... */ return "data"; }

Der richtige Ansatz ist, await zu verwenden und den Thread nicht zu blockieren.

Fehler Nr. 3: Asynchronität und UI.
In grafischen Anwendungen (WPF, WinForms) ist das Hauptproblem, den UI-Thread nicht einzufrieren. Wenn du im Hauptthread eine lang laufende oder blockierende Operation startest, hängt die ganze Anwendung. Asynchronität löst dieses Problem: Schwere Arbeit läuft im Hintergrund, die Oberfläche bleibt reaktionsfähig.

Fehler Nr. 4: Keine einheitliche Namenskonvention für asynchrone Methoden.
Wenn du asynchronen Methoden nicht das Suffix Async gibst, ist es leicht, zu verwechseln, welche Methode synchron und welche asynchron ist. Das führt zu versehentlichen Blockaden und Fehlern beim Aufruf. Benenne asynchrone Methoden immer mit dem Suffix Async.

2
Aufgabe
C# SELF, Level 59, Lektion 1
Gesperrt
Beispiel für eine einfache asynchrone Operation
Beispiel für eine einfache asynchrone Operation
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION