1. Einführung
Stell dir vor, Threads sind Leute in einer Schlange am Eisdielenstand. Manche stehen ruhig, andere machen Krach ("Ich muss gleich den Zug erwischen, bitte vorlassen!"), und der Verkäufer hört zwar alle, entscheidet aber manchmal, jemanden vorzuziehen — etwa eine Mutter mit weinendem Kind. Thread-Prioritäten sind ungefähr dasselbe.
In C# (genauer: in .NET) hat jeder Thread eine Priorität — einen Hinweis an das Betriebssystem, wie "wichtig" dieser Thread im Vergleich zu anderen ist. Das ist kein starres Gesetz für die OS (niemand garantiert, dass der höchst-priorisierte Thread immer alles bekommt), aber normalerweise bekommen Threads mit höherer Priorität mehr CPU-Zeit.
Wofür braucht man das wirklich?
- In UIs: z.B. sollte das Bildschirm-Update nicht durch lange Berechnungen mit niedriger Priorität ausgebremst werden.
- In Spielen: Rendering ist wichtiger als Hintergrund-Map-Generierung.
- Bei zeitkritischen Tasks: Geräte-Steuerung, Signalverarbeitung usw.
2. Thread-Prioritäten: wie das funktioniert
In C# ist die Arbeit mit Thread-Priorität simpel — das Objekt Thread hat die Eigenschaft Priority. Sie nimmt Werte aus dem Enum ThreadPriority:
| Wert | Beschreibung |
|---|---|
|
Am niedrigsten |
|
Unter dem Durchschnitt |
|
Normal (Standard) |
|
Über dem Durchschnitt |
|
Am höchsten |
Code-Beispiel:
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread lowPriorityThread = new Thread(PrintLowPriority);
Thread highPriorityThread = new Thread(PrintHighPriority);
lowPriorityThread.Priority = ThreadPriority.Lowest;
highPriorityThread.Priority = ThreadPriority.Highest;
lowPriorityThread.Start();
highPriorityThread.Start();
}
static void PrintLowPriority()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Niedrige Priorität: " + i);
Thread.Sleep(10); // Pause hinzufügen, damit man den Unterschied sieht
}
}
static void PrintHighPriority()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Hohe Priorität: " + i);
Thread.Sleep(10);
}
}
}
Zur Notiz
In der Praxis, besonders auf modernen Multi-Core-Systemen und in der verwalteten .NET-Umgebung, ist Priorität nur eine Empfehlung für das OS. Das OS (und .NET) versuchen, einem Thread mit hoher Priorität mehr Zeit zu geben, aber es gibt keine eiserne Garantie. Wenn dein Thread z.B. mit Priority Highest die CPU komplett belegt, kann die UI anfangen zu ruckeln und andere Threads leiden.
3. Ändern von Thread-Prioritäten
Threads und Prioritäten
graph LR
A[Thread 1 - Lowest] -->|Weniger CPU-Zeit| OS(Betriebssystem)
B[Thread 2 - Normal] --> OS
C[Thread 3 - Highest] -->|Mehr CPU-Zeit| OS
OS --> CPU(Prozessor)
Warum die Priorität ändern?
Es scheint vielleicht verlockend, immer Highest zu setzen, aber das ist eine schlechte Idee! Stell dir vor, alle Kunden im Laden rufen gleichzeitig "Bedient mich zuerst!".
Ändere Prioritäten nur wenn es begründet ist:
- Hintergrund-Thread für Logging — kann BelowNormal oder Lowest sein.
- Thread, der Benutzereingaben verarbeitet — AboveNormal.
- CPU-intensive Aufgaben, die nicht zeitkritisch sind — niedrige Priorität.
Tipp: Wenn die App ruckelt, prüfe, ob du "freche" Threads mit hoher Priorität hast, die den Rest blockieren!
4. Thread-Kategorien in .NET
In C# unterscheidet man traditionell zwei Arten von Threads: foreground und background. Überraschenderweise bedeutet "background" nicht unbedingt, dass der Thread "im Hintergrund arbeitet". Wir klären das jetzt.
Foreground-Threads (Hauptthreads)
- Standardmäßig sind alle von dir erstellten Threads foreground.
- Solange mindestens ein foreground-Thread im Prozess lebt, beendet sich die Anwendung nicht, selbst wenn alles andere "gestorben" ist.
- Beispiel: der Main-Thread (Main) und alle Threads, die über new Thread() ohne Änderungen erstellt wurden.
Background-Threads (Nebenthreads)
- Wenn alle foreground-Threads beendet sind und nur noch background-Threads übrig sind, beendet der Prozess einfach und die Threads werden ohne Vorwarnung abgebrochen.
- Verwendet für sekundäre Aufgaben: Logging, Hintergrund-Senden von Metriken usw.
- Um einen Thread zum Background-Thread zu machen: setze die Eigenschaft IsBackground auf true:
Thread t = new Thread(SomeMethod);
t.IsBackground = true;
t.Start();
Demonstration des Unterschieds
using System;
using System.Threading;
class Program
{
static void Main()
{
Thread backgroundThread = new Thread(() =>
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Background thread arbeitet... " + i);
Thread.Sleep(500);
}
Console.WriteLine("Background thread IST FERTIG.");
});
backgroundThread.IsBackground = true; // Thread als background markieren!
backgroundThread.Start();
Console.WriteLine("Main beendet sich in 1 Sekunde.");
Thread.Sleep(1000); // 1 Sekunde warten
Console.WriteLine("Main ist fertig. Was passiert mit dem background-thread?");
}
}
Was du sehen wirst:
Der Main-Thread kann enden und der background-Thread wird mitten in seiner Arbeit "abgeschaltet". Das ist praktisch für Aufgaben, die das Beenden der Anwendung nicht blockieren sollen.
5. Thread-Typen
ThreadPool
- ThreadPool ist ein Mechanismus, der einen Pool von Threads verwaltet, damit nicht zu viele Threads erstellt werden.
- Er wird verwendet, wenn viele kurze Tasks anfallen: parallele Request-Verarbeitung, asynchrone Operationen.
- Threads aus dem Pool sind immer background — sie hindern das Beenden der Anwendung nicht.
Beispiel: Code im ThreadPool starten
using System;
using System.Threading;
class Program
{
static void Main()
{
ThreadPool.QueueUserWorkItem(DoWorkInThreadPool, "hintergrundaufgabe");
Console.WriteLine("Main beendet sich.");
Thread.Sleep(500);
}
static void DoWorkInThreadPool(object? state)
{
Console.WriteLine("Thread aus dem Pool: " + state);
Thread.Sleep(1000); // Schlafen versuchen
Console.WriteLine("Thread aus dem Pool ist fertig!");
}
}
Worauf achten:
Wenn Main früher fertig wird, könnte ein Thread aus dem Pool abgebrochen werden. Wenn du sicherstellen musst, dass etwas abgeschlossen wird, nutze foreground-Threads oder warte explizit.
Evolution der Multithreading-Modelle: Tasks, async/await
In modernen Apps erstellt man selten Threads manuell (Thread). Meistens verwendet man Task und async-Methoden. Wichtig zu wissen:
- Threads aus dem Pool und Task-basierte Arbeiten laufen auf background-Threads.
- Für die meisten Anwendungsfälle muss man die Priorität nicht ändern — halte dich an gesunden Menschenverstand und Best Practices.
6. Nützliche Details
Thread-Eigenschaften und Verhalten
| Eigenschaft | Foreground Thread | Background Thread | ThreadPool Thread |
|---|---|---|---|
|
false standardmäßig | true | true |
| Beenden der Anwendung | Die Anwendung endet NICHT, solange mindestens ein foreground-Thread lebt | Die Anwendung endet, wenn alle foreground-Threads fertig sind | Die Anwendung endet, sobald Main fertig ist |
| Kontrolle | Volle Kontrolle | Volle Kontrolle | Keine direkte Kontrolle |
| Verwendungszweck | Lange, wichtige Tasks (z. B. DB-Server) | Nicht-kritische Aufgaben, Hintergrundoperationen, Logging | Kurzlebige Tasks, Task, asynchrone Operationen |
| Priorität | Kann gesetzt werden | Kann gesetzt werden | Nicht empfohlen zu ändern |
Unauffällige Tricks und Hinweise
- Priorität lässt sich nur bei normalen Threads (Thread) ändern, nicht bei Pool-Tasks (Task, ThreadPool).
- Threads aus dem Pool haben immer Priorität Normal (nicht veränderbar).
- Task und async/await sind moderner und verbergen viele Details zu Prioritäten und Hintergrundausführung "hinter den Kulissen".
7. Typische Fehler mit Prioritäten und Thread-Typen
Fehler Nr.1: Missbrauch von Prioritäten.
Allen Threads einfach Highest oder allen Lowest zu geben bringt nichts und kann die Balance zerstören und die Responsiveness der App verschlechtern.
Fehler Nr.2: Implizites Beenden von Background-Threads.
Wenn wichtige Arbeit (z. B. Daten speichern) in einem background-Thread läuft und du nicht auf dessen Ende wartest, riskierst du Datenverlust. Background-Threads werden beim Prozessende automatisch abgebrochen.
Fehler Nr.3: Überschätzte Erwartungen an Thread-Priorität.
Thread-Priorität (Priority) ist nur eine Empfehlung an das Betriebssystem, keine Garantie, dass der Thread zuerst ausgeführt wird.
GO TO FULL VERSION