Was „Keine Rückmeldung“ wirklich ist — Wie Windows entscheidet, dass eine App hängt, und Entwürfe, die nicht hängen
· Aktualisiert am: · Go Komura · Windows, Windows-Entwicklung, Fehleruntersuchung, Multithreading, WinForms, WPF, Win32-API, UI-Design
Änderungsverlauf (Erstfassung, veröffentlicht am 22. Aug 2026)
- Erstveröffentlichung
Diesen Artikel zitieren(DOI (registriertes Archiv): 10.5281/zenodo.22176661)
Die folgenden DOIs verweisen auf bereits archivierte Versionen, die vom aktuellen Text abweichen können. Verwenden Sie die URL dieser Seite, um auf den aktuellen Text zu verweisen.
Go Komura (2026). Was „Keine Rückmeldung“ wirklich ist — Wie Windows entscheidet, dass eine App hängt, und Entwürfe, die nicht hängen. KomuraSoft LLC. https://comcomponent.com/de/blog/windows-app-not-responding-hang-mechanism/
- DOI (registriertes Archiv)
- 10.5281/zenodo.22176661
- DOI (zuletzt registrierte Version)
- 10.5281/zenodo.22176662
„Während der Verarbeitung wird der Bildschirm weiß, und in der Titelleiste steht ‚Keine Rückmeldung‘.“ „Die Benutzer sagen, es hänge gelegentlich, aber auf der Entwicklungsmaschine reproduziert es sich nie.“ Das sind häufige Beschwerden über Windows-Geschäftsanwendungen.
Als Erstes gilt: Nicht die App selbst zeigt „Keine Rückmeldung“ an, sondern Windows. Windows erkennt, dass die Nachrichtenverarbeitung des Fensters stehen geblieben ist, und tauscht den ursprünglichen Bildschirm gegen ein Ersatzfenster. Der Schlüssel zur Ursache ist, was der UI-Thread, dem dieses Fenster gehört, tut, sodass er die nächste Nachricht nicht verarbeiten kann.12
Dieser Artikel richtet sich an Entwickler, die Geschäftsanwendungen unter Windows bauen, und an IT-Verantwortliche, die Anfragen zu hängenden Apps entgegennehmen. Er geht in der Reihenfolge Prüfung durch das Betriebssystem → Nachrichtenschleife → Eingrenzung der Ursache nach Kategorie → Entwürfe, die nicht hängen → Untersuchungsverfahren vor. Wenn Sie eine gerade hängende App untersuchen wollen, lesen Sie zuerst Kapitel 7.
1. Zuerst das Fazit: Die Anzeige nicht ausblenden, sondern den UI-Thread freihalten
Mechanismus, Reparatur und Untersuchung von „Keine Rückmeldung“ werden übersichtlicher, wenn man sie so aufteilt.
| Was Sie wissen oder lösen wollen | Das Erste, das festzuhalten ist | Ausführliche Erklärung |
|---|---|---|
| Wonach schaut Windows, um ohne Rückmeldung zu urteilen? | Es schaut auf das Fenster und die Nachrichtenverarbeitung des GUI-Threads, dem es gehört. Die Prüfung gilt nicht für den Prozess als Ganzes | Kapitel 2 |
| Warum bleiben Schaltflächen und Neuzeichnen stehen? | Der UI-Thread kommt aus einer Ereignisbehandlung oder einem Warten nicht zurück und kann die nächste Nachricht nicht abholen | Kapitel 3 und 4 |
| Bei langwieriger Arbeit soll der Bildschirm nicht einfrieren | CPU-Arbeit und APIs, die nur synchron existieren, auf einen Worker auslagern; für I/O asynchrone APIs nutzen | Kapitel 5 |
| Darf man die Anzeige nur mit DoEvents oder einer Einstellung umgehen? | Das lädt Wiedereintrittsfehler ein oder blendet die Anzeige nur aus. Keine Ursachenlösung | Kapitel 6 |
| Die Ursache gelegentlicher Hänger finden | Den Dump im hängenden Moment nehmen, bevor Sie beenden oder neu starten | Kapitel 7 |
Das Prinzip der Gegenmaßnahme ist: Auf dem UI-Thread nicht lange warten und nicht schwer rechnen. Der UI-Thread übernimmt Eingabe, Zeichnen, Fortschrittsanzeige und das Annehmen von Abbruch und wird von zeitaufwendiger Arbeit getrennt.3
Außerdem sind die Zeit bis zur Anzeige „Keine Rückmeldung“ und die Antwortzeit, die sich noch angenehm bedienen lässt, zwei verschiedene Dinge. Unter 5 Sekunden zu bleiben reicht nicht. Abschnitt 4.5 erklärt diesen Unterschied.
2. „Keine Rückmeldung“ ist das Urteil des Betriebssystems: die 5-Sekunden-Regel und der Ersatzbildschirm
2.1 Die Einheit der Beurteilung ist nicht der ganze Prozess, sondern das Fenster und sein besitzender Thread
In Microsofts Beschreibung von IsHungAppWindow gilt ein Fenster als ohne Rückmeldung, wenn es die folgenden Bedingungen erfüllt.1
| Bedingung | Inhalt |
|---|---|
| Wartet nicht auf Eingabe | Es befindet sich nicht im Zustand des Wartens auf Eingabe |
| Nicht in der Startverarbeitung | Die App ist nicht in ihrer Startverarbeitung |
| Holt keine Nachrichten ab | Es hat PeekMessage für das interne Timeout von 5 Sekunden nicht aufgerufen |
Das Betriebssystem schaut nicht darauf, was die App berechnet, sondern darauf, ob die Nachrichtenschleife läuft. Die Nachrichtenschleife ist der Mechanismus, der Eingabe- und Neuzeichenanforderungen der Reihe nach abholt und verarbeitet. Kapitel 3 erklärt, wie das konkret abläuft.
Dieselbe Dokumentation hält auch fest, dass der Wert von 5 Sekunden sich in Zukunft ändern kann. Es ist das interne Timeout der Prüfung ohne Rückmeldung, kein Entwurfsmaßstab, der sagt „die UI darf bis zu 5 Sekunden stehen bleiben“.1
Die Prüfung gilt nicht pro Prozess. In einer App mit mehreren UI-Threads kann ein Fenster hängen, während Fenster, die anderen Threads gehören, weiterlaufen. Auch in der Untersuchung müssen Sie über den Prozess hinausgehen und den Thread identifizieren, dem das hängende Fenster gehört.
2.2 Der milchig-weiße Bildschirm ist ein „Geisterfenster“
Wenn ein Fenster der obersten Ebene als ohne Rückmeldung beurteilt wird, blendet Windows das ursprüngliche Fenster aus und tauscht es gegen ein Geisterfenster mit derselben Z-Reihenfolge, Position, Größe und Erscheinung. Das „(Keine Rückmeldung)“ im Titel und das milchig-weiße Aussehen unter dem Aero-Design kommen von diesem Ersatzbildschirm, nicht von der hängenden App.2
| Zustand des Bildschirms | Was der Benutzer sieht |
|---|---|
| Das ursprüngliche Fenster der App | Kann Nachrichten nicht verarbeiten; reagiert nicht auf Klicks oder Neuzeichnen |
| Das Geisterfenster, das das Betriebssystem bereitstellt | Begrenzte Operationen wie Verschieben, Größenänderung, Minimieren und Schließen |
Dass sich das Ersatzfenster bewegen lässt, bedeutet nicht, dass der Inhalt der App wieder arbeitet. Das Betriebssystem übernimmt nur die Mindestoperationen.24
Auch eine Beschwerde, „die Anzeige kommt zu früh“, fassen Sie zuerst als Problem auf, dass der UI-Thread lange nicht zur Nachrichtenverarbeitung zurückkehrt. Die stehende Verarbeitung zu untersuchen kommt vor dem Unterdrücken der Anzeige.
2.3 Dass es unter dem Debugger nicht erscheint, heißt nicht, dass es nicht hängt
Während ein Debugger verbunden ist, legt das Betriebssystem keine Geisterfenster an. Deshalb kann ein Unterschied wie „unter dem Debugger erscheint es nicht, in der normalen Ausführung geht es auf Keine Rückmeldung“ trotzdem bedeuten, dass der UI-Thread genauso blockiert ist und sich nur die Anzeige unterscheidet.2
Es gibt auch eine API, die den Tausch gegen ein Geisterfenster pro Prozess deaktiviert, aber sie ist keine API, die den Hänger selbst behebt. Abschnitt 6.2 trennt den Verwendungszweck.
3. Warum der Bildschirm stehen bleibt: UI-Thread und Nachrichtenschleife
3.1 Eingabe und Neuzeichnen verarbeitet derselbe Thread
Windows-GUI-Apps sind ereignisgetrieben. Sie empfangen Nachrichten für Maus, Tastatur, Neuzeichenanforderungen, Timer und so weiter und führen die jeweils zugehörige Verarbeitung aus. Jeder Thread, der ein Fenster anlegt, hat eine Nachrichtenwarteschlange und führt eine Schleife wie die folgende aus.5
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg); // die Fensterprozedur wird aufgerufen
}
GetMessage holt eine Nachricht aus der Warteschlange, und DispatchMessage ruft die Fensterprozedur des Fensters auf. Die Fensterprozedur ist die Funktion, die die Verarbeitung zu einer Nachricht ausführt. Die Behandlung eines Schaltflächenklicks, das Neuzeichnen und WinForms- und WPF-Ereignishandler hängen alle an dieser Nachrichtenverarbeitung auf dem UI-Thread.6
flowchart TB
accTitle: Grundstruktur der Nachrichtenschleife
accDescr: Das Betriebssystem legt Maus, Tastatur und andere Eingabe in die Nachrichtenwarteschlange des Threads; die Schleife des UI-Threads holt sie mit GetMessage ab, ruft die Fensterprozedur mit DispatchMessage auf und kehrt zum Anfang der Schleife zurück, wenn die Verarbeitung fertig ist
os["Betriebssystem (Eingabe, Neuzeichenanforderungen, Timer)"] --> q["Nachrichtenwarteschlange des Threads"]
q --> gm["Mit GetMessage abholen"]
gm --> dm["DispatchMessage"]
dm --> wp["In der Fensterprozedur verarbeiten"]
wp --> gm
Abbildung 1: Eine Nachricht abholen, in der Fensterprozedur verarbeiten und zur Schleife zurückkehren. Dieser Umlauf trägt Eingabe und Zeichnen.
Was geschieht nun, wenn aus einem Klickhandler eine lange Verarbeitung aufgerufen wird? Bis diese Verarbeitung zurückkehrt, kann der UI-Thread die nächste Nachricht nicht abholen. Klicks und Neuzeichenanforderungen, die danach eintreffen, lassen sich nicht mehr verarbeiten. Das ist die Grundstruktur eines „eingefrorenen Bildschirms“.
3.2 PostMessage und SendMessage kehren zu unterschiedlichen Zeitpunkten zurück
Die Zustellung von Nachrichten hat zwei Wege: einen, der die Nachricht auf die Warteschlange legt, und einen, der auf den Abschluss der Verarbeitung wartet.57
| API | Verhalten | Wann der Aufrufer zurückkehrt |
|---|---|---|
PostMessage |
Legt die Nachricht auf die Warteschlange; die Empfängerseite holt sie ab und verarbeitet sie | Kehrt zurück, sobald die Nachricht eingereiht ist. Wartet nicht auf den Abschluss der Verarbeitung |
SendMessage |
Sendet die Nachricht an die Fensterprozedur und lässt sie ausführen | Kehrt nicht zurück, bis die Fensterprozedur fertig ist |
Besonders wenn SendMessage an ein Fenster auf einem anderen Thread gesendet wird, muss auch die Senderseite warten, bis die Empfängerseite die Nachricht verarbeiten kann. Dieser Unterschied führt direkt in den Deadlock von Abschnitt 4.3.7
4. Ursachen nach Kategorie: fünf Muster, die den UI-Thread blockieren
Das Aussehen von „Keine Rückmeldung“ ist dasselbe, aber was blockiert, unterscheidet sich. Trennen Sie zuerst die Kandidaten und bestätigen Sie sie schließlich mit den Dumps und Aufzeichnungen aus Kapitel 7.
| Kandidatenursache | Was auf dem UI-Thread geschieht | Typisches Erscheinungsbild |
|---|---|---|
| Synchrones I/O oder Netzaufruf | Wartet auf den Abschluss von Datei, Datenbank, Web-API oder Ähnlichem | Auf der Entwicklungsmaschine schnell, hängt aber in der Produktion oder in bestimmten Umgebungen |
| Sperrwarten | Kann eine vom Worker gehaltene Sperre nicht erwerben und wartet | Hängt selten, je nachdem, wie sich Verarbeitungen überlappen |
SendMessage zwischen Threads |
Wartet darauf, dass ein anderer Thread die Nachricht verarbeitet | Gerät in gegenseitiges Warten oder in einen Broadcast |
| Aufruf in ein COM-STA | Wartet auf die Nachrichtenverarbeitung des Ziel-STA | Ein stehender UI-Thread greift auf COM-Aufrufe anderer Threads über |
| Aneinanderreihung kurzer Verarbeitungen | Jede einzelne ist kurz, aber die Hintereinanderausführung kehrt nicht zur Schleife zurück | Die Bedienung wird nur träge, wenn die Stückzahl wächst |
4.1 Synchrones I/O und Netzaufrufe
Das häufigste Muster ist, im Klickhandler einer Schaltfläche das Lesen oder Schreiben einer großen Datei, eine Datenbankabfrage, eine Web-API oder den Zugriff auf ein Netzlaufwerk synchron auszuführen.
Was auf der Entwicklungsmaschine in einem Bruchteil einer Sekunde fertig ist, kann unter Produktionslatenz oder bei einem Problem des Dateiservers zu einem Warten von mehreren zehn Sekunden werden. Netzlaufwerke haben bei Verbindungsabbruch lange Timeouts, was das Symptom verschlimmert. „Auf der Entwicklungsmaschine war es schnell“ ist kein Grund, auf dem UI-Thread zu warten.
4.2 Der UI-Thread wartet auf eine Sperre, die ein Worker hält
Sperren, die gemeinsame Daten schützen, können die UI ebenfalls anhalten. Wenn ein Worker eine Sperre lange hält und der UI-Thread dieselbe Sperre nehmen will, wartet der UI-Thread, bis er sie erwerben kann.
Auch wenn Sie die Arbeit auf einen Worker verlagern: Der Bildschirm wird nicht frei, solange der UI-Thread auf das Ende dieser Arbeit oder auf die Freigabe der Sperre wartet. Die Disziplin von Sperren behandeln wir ausführlich in der praktischen Multithreading-Serie.
4.3 Über SendMessage auf den Abschluss des anderen warten
Der klassische Deadlock ist die Kombination, in der der UI-Thread auf das Ende eines Workers wartet und dieser Worker SendMessage an die UI sendet und wartet. Die UI ist im Warten und kann keine Nachrichten verarbeiten, und der Worker kommt aus SendMessage nicht zurück, sodass keiner von beiden vorankommt.75
sequenceDiagram
accTitle: Deadlock durch SendMessage zwischen Threads
accDescr: Wenn ein Worker-Thread SendMessage an das Fenster des UI-Threads sendet, während der UI-Thread blockiert auf das Ergebnis des Workers wartet, warten beide auf den Abschluss des anderen und es entsteht ein Deadlock
participant U as UI-Thread
participant W as Worker-Thread
U->>U: Auf den Abschluss des Workers warten (blockiert)
W->>U: SendMessage (kehrt erst nach der Verarbeitung zurück)
Note over U: Kann Nachrichten nicht verarbeiten (wartet)
Note over W: Kann aus SendMessage nicht zurückkehren
Note over U,W: Gegenseitiges Warten: Deadlock
Abbildung 2: Die Auslagerung auf einen Worker allein reicht nicht. Wenn UI und Worker auf den Abschluss des anderen warten, entsteht ein Deadlock.
Auch das Senden an HWND_BROADCAST braucht Vorsicht. Ein einziges nicht antwortendes Fenster reicht, um die Senderseite mitzureißen. Wo Sie nicht weiterwarten können, ziehen Sie SendMessageTimeout mit Timeout oder PostMessage, das nicht auf den Abschluss wartet, in Betracht.8
4.4 Ein COM-STA bleibt stehen und reißt andere Threads mit
Aufrufe von anderen Threads in ein STA-Objekt werden als Fensternachrichten zugestellt. Deshalb blockieren COM-Aufrufe dorthin, wenn der UI-Thread (ein STA) blockiert ist. Es ist eine Struktur, in der nicht nur die UI, sondern auch die Aufruferseite in den Wartezustand gerät.
Den Zusammenhang von Apartments und Nachrichtenverarbeitung erklärt der Artikel zu COM STA/MTA.
4.5 „Einmal ist es ein Augenblick“ hundertmal hintereinander
Eine synchrone Verarbeitung von 50 ms addiert sich bei 100 Wiederholungen auf 5 Sekunden. Beurteilen Sie nicht, indem Sie einzelne Funktionen messen und sie als kurz einstufen; denken Sie in der Gesamtzeit, bis der UI-Thread zur Nachrichtenschleife zurückkehrt.
Spürbare Trägheit beginnt bei etwa 100 ms. Nehmen Sie nicht die 5 Sekunden der Prüfung ohne Rückmeldung als Ziel; entwerfen Sie so, dass der UI-Thread nur im Millisekundenbereich blockiert sein darf.
5. Entwürfe, die nicht hängen: Ausführung der Arbeit und Aktualisierung des Bildschirms trennen
5.1 Berechnung und synchrone APIs von asynchronem I/O trennen
Das Prinzip der Gegenmaßnahme ist, zeitaufwendige Arbeit vom UI-Thread herunterzunehmen. Nicht alles wird jedoch auf dieselbe Weise verlagert.
| Art der Arbeit | Grundlegende Behandlung in C# | Rolle des UI-Threads |
|---|---|---|
| CPU-lastige Berechnung | Mit Task.Run auf einen Worker-Thread verlagern |
Den Abschluss asynchron abwarten und das Ergebnis anzeigen |
| Verarbeitung, für die nur eine synchrone API existiert | Auf einen Worker-Thread auslagern | Die UI nicht durch synchrones Warten auf den Abschluss blockieren |
| I/O mit asynchroner API | GetStringAsync oder Ähnliches mit await |
Während des I/O-Wartens zur Nachrichtenverarbeitung zurückkehren |
| Eingabe, Zeichnen, Fortschritt, Abbruch | Auf dem UI-Thread übernehmen | Keine lange Berechnung und kein synchrones I/O untermischen |
Bei asynchronem I/O muss kein Thread nur zum Warten belegt werden. Auch das Codebeispiel unten trennt: Berechnung über Task.Run, HTTP-Kommunikation über die native asynchrone API.3
5.2 In C# mit async/await zur UI zurückkehren und aktualisieren
Das nächste Beispiel trennt in einem WinForms-Klickhandler schwere Arbeit und UI-Aktualisierung. Weil dieses Beispiel von einem UI-Ereignishandler startet und den Ausführungskontext der UI behält, kehrt die Fortsetzung nach await auf den UI-Thread zurück und kann Steuerelemente aktualisieren.3
private async void RunButton_Click(object sender, EventArgs e)
{
runButton.Enabled = false;
try
{
// CPU-lastige Verarbeitung und APIs, die nur synchron existieren, per Task.Run auf den Worker
var result = await Task.Run(() => HeavyCalculation(input));
// Für I/O eine nativ asynchrone API nutzen (sie verbraucht auch keinen Thread)
var data = await httpClient.GetStringAsync(url);
// Nach await sind wir auf dem UI-Thread, Steuerelemente dürfen direkt angefasst werden
resultLabel.Text = result + data.Length.ToString();
}
catch (Exception ex)
{
// Eine Ausnahme, die aus einem async-void-Handler entweicht, stürzt die App ab. Hier abfangen
MessageBox.Show($"Die Verarbeitung ist fehlgeschlagen: {ex.Message}");
}
finally
{
runButton.Enabled = true;
}
}
Vier Punkte sind festzuhalten.
| Stelle | Absicht |
|---|---|
Den Abschluss mit await abwarten |
Den UI-Thread bei einem Warten zur Nachrichtenschleife zurückgeben |
Den Bildschirm nach await aktualisieren |
Steuerelemente nicht vom Worker aus anfassen |
| Die Schaltfläche während der Ausführung deaktivieren | Verhindert, dass dieselbe Verarbeitung wiederholt gestartet wird |
catch und finally |
Lässt Ausnahmen nicht aus dem async void-Handler entweichen und stellt den Schaltflächenzustand wieder her |
Das ist ein Codebeispiel für die Rollenteilung; die Umsetzung von HeavyCalculation und Ähnlichem, der Umgang mit dem Schließen des Formulars sowie Fortschritt und Abbruch sind weggelassen. Fortschritt und Unterbrechung, die langwierige Arbeit braucht, bauen Sie gesondert ein, wie in Abschnitt 5.4.
WinForms-Steuerelemente und WPF-Elemente behandelt man von dem Thread aus, der sie angelegt hat. Sie direkt vom Worker anzufassen führt zu Ausnahmen oder undefiniertem Verhalten. Um ausdrücklich auf den UI-Thread zurückzukehren, nutzen Sie Control.Invoke / BeginInvoke in WinForms und Dispatcher.InvokeAsync in WPF.3
5.3 In Win32 die Fertigstellung mit PostMessage mitteilen
Die Rollenteilung ist in nativem Win32 dieselbe. Übergeben Sie die Arbeit an einen Worker, und wenn sie fertig ist, senden Sie mit PostMessage eine eigene Fertigstellungsnachricht und aktualisieren den Bildschirm in der Fensterprozedur des UI-Threads. Weil PostMessage zurückkehrt, sobald die Nachricht eingereiht ist, wartet der Worker nicht auf den Abschluss der UI-Verarbeitung.7
flowchart TB
accTitle: Rollenteilung einer App, die nicht hängt
accDescr: Der UI-Thread übernimmt nur Eingabe, Fortschrittsanzeige und das Annehmen von Abbruch; ein Worker-Thread führt die schwere Verarbeitung aus und gibt die Fertigstellung über PostMessage oder eine await-Fortsetzung an den UI-Thread zurück
ui["UI-Thread: Eingabe, Fortschritt, Abbruch"] -->|"Arbeit übergeben"| w["Worker-Thread: schwere Verarbeitung"]
w -->|"PostMessage / await-Fortsetzung"| ui
ui -.-> ng["Kein synchrones I/O und keine lange Berechnung auf dem UI-Thread"]
Abbildung 3: Schwere Berechnung und synchrone Verarbeitung an den Worker übergeben und Bildschirmaktualisierungen auf den UI-Thread zurückholen. Asynchrones I/O wartet man wie in Abschnitt 5.1 mit einer asynchronen API.
Das Warten auf den Worker selbst schreiben Sie nach der Disziplin, die der Artikel zu Bedingungsvariablen behandelt. Wichtig ist auch, dass der UI-Thread den Worker nicht wieder synchron abwartet.
5.4 Fortschritt und Abbruch von Anfang an entwerfen
Auch wenn der Bildschirm nicht einfriert: Wenn sich lange nichts ändert, kann der Benutzer nicht erkennen, ob noch etwas läuft. Deshalb entwerfen Sie Fortschrittsanzeige und Abbruchannahme als Teil der Verarbeitung.
| Richtung der Mitteilung | Mechanismus | Rolle |
|---|---|---|
| Worker → UI | IProgress<T> |
Den Fortschritt an den Bildschirm melden |
| UI → Worker | CancellationToken |
Die Aufforderung zum Anhalten übermitteln |
Der Worker bricht an einer sinnvollen Stelle ab und räumt auf. Mittel für Fortschritt und Abbruch verringern die Situationen, in denen Benutzer zum erzwungenen Beenden greifen. Erzwungenes Beenden kann zu Datenbeschädigung führen.
6. Zwei Wege, die wie Auswege aussehen, und ihre Grenzen
6.1 DoEvents holt andere Ereignisse mitten in die Verarbeitung
Wenn Sie während schwerer Arbeit Application.DoEvents() oder eine PeekMessage-Schleife einschieben, werden Nachrichten verarbeitet und die Anzeige „Keine Rückmeldung“ vermieden. Allerdings läuft dann ein anderer Ereignishandler, während die ursprüngliche Verarbeitung noch nicht fertig ist. Das ist Wiedereintritt.
Zum Beispiel wird DoEvents mitten in der Datenverarbeitung aufgerufen, und ein aufgestauter zweiter Klick auf die Schaltfläche wird verarbeitet. Dieser Handler schreibt dieselben Daten um, und danach setzt die ursprüngliche Verarbeitung fort. In dieser Reihenfolge ist der Zustand, den die ursprüngliche Verarbeitung angenommen hat, bereits verloren.
Es treten nicht nur Schaltflächen wieder ein. Auch das Schließen des Formulars und Timer kommen herein. Fehler wie zerstörte Daten während der Verarbeitung oder eine Ausnahme durch das Anfassen eines geschlossenen Formulars sind zeitabhängig und schwer zu reproduzieren und oft schwieriger zu untersuchen als „Keine Rückmeldung“.
Das Prinzip ist, die Arbeit zu trennen, nicht die Pumpe von Hand zu drehen. Manuelle Nachrichtenverarbeitung bleibt auf begrenzte Strukturen wie einen modalen Fortschrittsdialog beschränkt. Für gewöhnliche langwierige Arbeit nutzen Sie die Trennung und die Wiedereintrittsvermeidung aus Kapitel 5.
6.2 Geisterfenster zu deaktivieren bringt die Bedienung nicht zurück
DisableProcessWindowsGhosting ist eine API, die den Tausch gegen ein Geisterfenster für den aufrufenden Prozess deaktiviert. Die Deaktivierung gilt für die Lebensdauer des Prozesses.4
Sie ist für besondere Zwecke gedacht, etwa Kioskterminals, bei denen Sie vermeiden wollen, dass das Betriebssystem ein bedienbares Ersatzfenster aufstellt. Die Tatsache, dass die Nachrichtenverarbeitung steht, ändert sich nicht, und der Benutzer verliert auch den Weg zum Verschieben oder Beenden, den das Geisterfenster bot. Für die Gegenmaßnahme gegen „Keine Rückmeldung“ in gewöhnlichen Apps ist sie nicht gedacht.
| Methode | Was sich ändert | Was bleibt |
|---|---|---|
Die Pumpe von Hand mit DoEvents oder Ähnlichem drehen |
Auch mitten in der Verarbeitung andere Nachrichten behandeln | Beliebige Ereignisse treten wieder ein und können den Zustand zerstören |
DisableProcessWindowsGhosting |
Stoppt den Tausch gegen ein Ersatzfenster durch das Betriebssystem | Der UI-Thread bleibt blockiert |
| Zeitaufwendige Arbeit von der UI trennen | Lässt den UI-Thread zu Eingabe und Zeichnen zurückkehren | Fortschritt, Abbruch und Wiedereintrittsvermeidung müssen ebenfalls entworfen werden |
7. Untersuchungsverfahren: Den hängenden Moment festhalten, bevor Sie schließen
7.1 Zuerst einen Dump nehmen
Bei der Untersuchung von „es hängt gelegentlich“ ist das Wertvollste der Threadzustand im hängenden Moment selbst. Sobald Sie beenden oder neu starten, ist dieser Zustand verloren.
Klicken Sie auf der Registerkarte „Details“ des Task-Managers mit der rechten Maustaste auf den Zielprozess und wählen Sie „Abbilddatei erstellen“. Damit erhalten Sie einen vollständigen Dump mit den Stacks aller Threads. Teilen Sie den Leuten, die die Anfragen entgegennehmen, ebenfalls das Verfahren mit: „Wenn es hängt, vor dem Schließen einen Dump nehmen.“
Zum Aufbau der Sammlung siehe den Artikel zum Sammeln von Absturzabbildern.
7.2 Den Thread ansehen, dem das hängende Fenster gehört
Öffnen Sie den Dump in WinDbg und prüfen Sie den Stack des UI-Threads. Der Thread, der die Nachrichtenschleife dreht, ist gewöhnlich Thread 0, aber es gibt Apps mit mehreren UI-Threads, also entscheiden Sie nicht nur nach der Nummer; sehen Sie den Thread an, dem das hängende Fenster gehört.
| Was auf dem Stack zu sehen ist | Was als Nächstes zu prüfen ist |
|---|---|
Warten in ReadFile oder einer Netz-API |
Dateizugriff, synchrones I/O, Warten auf eine Netzantwort |
Warten in einer WaitFor…-Funktion |
Die Sperre oder das Synchronisierungsobjekt. Auf wessen Abschluss gewartet wird und was die Gegenseite tut |
Warten innerhalb von SendMessage |
Ob der Zielthread Nachrichten verarbeiten kann. Ob sie gegenseitig warten |
Auf dem Stack des UI-Threads steht, worauf er wartet. Bei Verdacht auf Deadlock gleichen Sie die Warteziele beider Threads ab, nicht nur eines. Wie man sie liest, erklärt der einführende WinDbg-Artikel.
flowchart TB
accTitle: Grundverfahren zur Untersuchung von Keine Rückmeldung
accDescr: Im hängenden Moment einen Dump nehmen, den Stack des UI-Threads ansehen, feststellen, ob er in synchronem I/O, einem Sperrwarten oder SendMessage zwischen Threads steckt, und das zur passenden Entwurfskorrektur führen
hang["Der hängende Moment"] --> dump["Dump nehmen (vor dem Schließen)"]
dump --> stack["Den Stack des UI-Threads ansehen"]
stack --> io["Synchrones I/O oder Netzwarten"]
stack --> lock["Sperrwarten"]
stack --> sm["SendMessage zwischen Threads"]
io -.-> fix["Die betreffende Stelle auf einen Worker auslagern"]
lock -.-> fix
sm -.-> fix
Abbildung 4: Vom Dump des hängenden Moments aus verfolgen, worauf der UI-Thread wartet. Bei I/O asynchron machen oder auslagern; bei Sperren und SendMessage auch die Warte-Struktur prüfen.
7.3 Live-Prüfung und Zeitreihenanalyse unterscheiden
Neben Dumps gibt es einen Weg, einen laufenden Prozess an Ort und Stelle anzusehen, und einen Weg, den Zeitverlauf aufzuzeichnen.
| Was Sie herausfinden wollen | Methode |
|---|---|
| Den hängenden Moment festhalten und später untersuchen | Einen Dump nehmen und in WinDbg analysieren |
| Threads und Stacks eines lebenden Prozesses an Ort und Stelle sehen | Process Explorer nutzen |
| Ständige Trägheit oder das Warten des UI-Threads über die Zeit verfolgen | Eine Aufzeichnung mit WPR nehmen und in WPA analysieren |
Wie man eine Zeitreihe aufnimmt, behandelt WPR/WPA in der Praxis.
7.4 Kandidaten vom Symptom eingrenzen und mit Belegen bestätigen
Reproduktionsbedingungen sind ebenfalls ein Einstieg in die Untersuchung. Entscheiden Sie die Ursache jedoch nicht nur vom Symptom; gleichen Sie sie mit Dumps und Aufzeichnungen ab.
| Symptom | Zuerst verdächtigen | Wo zu prüfen ist |
|---|---|---|
| Hängt bei einer bestimmten Operation immer | Synchrones I/O oder Ähnliches in diesem Handler | Die Verarbeitung zu dieser Operation und der Stack des UI-Threads |
| Hängt selten, ohne Zusammenhang mit einer Operation | Sperrreihenfolge oder ein Deadlock durch SendMessage zwischen Threads |
Die Stacks beider Threads, die aufeinander warten |
| Hängt nur in einer bestimmten Umgebung | Warten und Timeouts durch Netzlaufwerke, Proxys, Antivirensoftware und Ähnliches | Die API, auf die gewartet wird, und ihre Antwortzeit in dieser Umgebung |
8. Zusammenfassung
„Keine Rückmeldung“ ist ein Mechanismus, bei dem Windows feststellt, dass die Nachrichtenverarbeitung eines Fensters stehen geblieben ist, und einen Ersatzbildschirm aufstellt. Die Einheit der Beurteilung ist das Fenster und der GUI-Thread, dem es gehört, nicht der Prozess als Ganzes. Das interne Timeout von 5 Sekunden ist ein Wert, der sich in Zukunft ändern kann, und ist von einer angenehmen UI-Antwortzeit zu unterscheiden.12
Zu beheben ist nicht die Anzeige, sondern die Berechnung oder das Warten, die den UI-Thread lange belegen. CPU-Arbeit und APIs, die nur synchron existieren, gehen auf den Worker, asynchrones I/O auf asynchrone APIs, und Bildschirmaktualisierungen kehren auf den UI-Thread zurück. Fortschritt, Abbruch und Wiedereintrittsvermeidung entwerfen Sie als Set. Das Umgehen mit DoEvents oder das Deaktivieren von Geisterfenstern ist kein Ersatz dafür.
Wenn die Ursache unbekannt ist, beginnen Sie damit, vor dem Beenden einen Dump zu nehmen und zu sehen, worauf der Thread wartet, dem das hängende Fenster gehört. Ersetzen Sie „es sieht kaputt aus“ durch „der UI-Thread kann nicht zur Nachrichtenverarbeitung zurückkehren“, und Untersuchungsorte und Entwurfskorrekturen hängen zusammen.
Weiterführende Artikel
- Scheinwecken — Warum Bedingungsvariablen „ohne Benachrichtigung“ aufwachen und wie Sie unter Windows richtig warten
- Praktische Best Practices für Multithreading: .NET-Edition — Was Sie entscheiden sollten, bevor Sie weitere Threads hinzufügen
- Grundlagenwissen zu COM STA/MTA – Threadmodelle und wie man Hänger vermeidet
- Absturz-Dumps mit WinDbg + SOS lesen — Ein praxistauglicher Leitfaden zur Analyse nach der Sammlung
- Process Explorer / Handle / VMMap in der Praxis — Hängern, Lecks und „Datei wird verwendet“ vom aktuellen Zustand aus nachjagen
- Windows-Herunterfahren aus Sicht Ihrer App — Beendigungsbenachrichtigungen, Neustarts und Stromverlust richtig überstehen
Zugehörige Beratungsfelder
KomuraSoft LLC übernimmt Ursachenuntersuchungen (Dump-Analyse und Trace-Analyse) von Geschäftsanwendungen, die „gelegentlich hängen“ oder auf „Keine Rückmeldung“ gehen, das Umbauen von Legacy-UI-Code voller synchroner Verarbeitung auf async/await und Worker-Thread-Trennung sowie Reviews von UI-Entwürfen, die nicht einfrieren. Auch wenn das Reproduktionsverfahren noch unbekannt ist, können wir ab dem Entwurf helfen, wie Belege gesammelt werden.
- Fehleruntersuchung und Ursachenanalyse
- Technische Beratung und Design-Review
- Windows-Anwendungsentwicklung
- Kontakt
Quellen
-
Microsoft Learn, IsHungAppWindow function (winuser.h). Zum Urteilskriterium, dass eine App als ohne Rückmeldung behandelt wird, wenn sie „nicht auf Eingabe wartet, nicht in der Startverarbeitung ist und PeekMessage für das interne Timeout von 5 Sekunden nicht aufgerufen hat“; dazu, dass dieses 5-Sekunden-Kriterium sich ändern kann; sowie dazu, dass die Funktion für ein Geisterfenster immer TRUE zurückgibt. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, GetMessage function (winuser.h). Dazu, dass das System ein Fenster der obersten Ebene als ohne Rückmeldung behandelt, wenn es mehrere Sekunden auf Nachrichten nicht antwortet, und es durch ein Geisterfenster derselben Z-Reihenfolge, Position, Größe und Erscheinung ersetzt; dazu, dass der Benutzer es nur verschieben, in der Größe ändern oder schließen kann; sowie dazu, dass ein Geisterfenster nicht angelegt wird, während ein Debugger verbunden ist. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, How to make thread-safe calls to controls (Windows Forms). Dazu, dass WinForms-Steuerelemente nicht sicher von einem anderen Thread als dem, der sie angelegt hat, angefasst werden dürfen; zur Nutzung von Invoke/BeginInvoke für Aktualisierungen von einem anderen Thread; sowie zu sicheren asynchronen Mustern mit async/await oder BackgroundWorker. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, DisableProcessWindowsGhosting function (winuser.h). Dazu, dass Sie für den aufrufenden GUI-Prozess die Geisterfensterfunktion deaktivieren können, die ein Fenster ohne Rückmeldung minimierbar, verschiebbar und schließbar macht; sowie dazu, dass die Deaktivierung für die Lebensdauer des Prozesses gilt. ↩ ↩2
-
Microsoft Learn, About Messages and Message Queues. Dazu, dass Windows-Apps ereignisgetrieben sind und die Fensterprozedur Nachrichten verarbeitet; zur Unterscheidung zwischen eingereihten Nachrichten und direkt gesendeten Nachrichten; zum Tausch eines Fensters ohne Rückmeldung gegen ein Geisterfenster; sowie zum Abschnitt über Deadlock durch Threads, die einander Nachrichten senden. ↩ ↩2 ↩3
-
Microsoft Learn, Using Messages and Message Queues. Zu einer typischen Umsetzung einer Nachrichtenschleife mit GetMessage, TranslateMessage und DispatchMessage sowie dazu, wie man eine Nachrichtenwarteschlange prüft. ↩
-
Microsoft Learn, SendMessage function (winuser.h). Dazu, dass SendMessage die Fensterprozedur des angegebenen Fensters aufruft und nicht zurückkehrt, bis die Verarbeitung fertig ist; dazu, dass ein Senden an ein Fenster auf einem anderen Thread den Sender warten lässt, bis dieser Thread die Nachricht verarbeitet; sowie zum Unterschied zu PostMessage, das die Nachricht auf die Warteschlange legt, ohne auf eine Antwort zu warten. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, SendMessageTimeout function (winuser.h). Dazu, eine Nachricht mit einem Timeout senden zu können; sowie zu einem Flag (SMTO_ABORTIFHUNG), das ohne Warten zurückkehrt, wenn das Fenster keine Rückmeldung gibt (als hängend beurteilt wurde). ↩
Verwandte Artikel
Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.
DllMain und die Ladersperre — Der wahre Grund, warum man sagt, in der DLL-Initialisierung nichts zu tun
Warum Sie aus DllMain weder LoadLibrary aufrufen noch mit Threads synchronisieren dürfen. Anhand von Primärquellen erklärt dieser Artikel...
Ende der Wartung von Windows-Druckertreibern — Wie Geschäftsanwendungen Bericht- und Etikettendruck vorbereiten
Microsoft stellt v3/v4-Druckertreiber schrittweise ein. Was Windows protected print mode entfernt und wie Geschäftsanwendungen Bericht- u...
Win32-Thread-Pool-API — Nebenläufigkeit ohne eigene Threads, mit CreateThreadpoolWork
Häufen sich in nativem Code die CreateThread-Aufrufe? Dieser Artikel erklärt anhand von Primärquellen die in Vista neu gestaltete Win32-T...
Apps, die nach dem Fortsetzen aus dem Schlaf kaputtgehen — Energieereignisse und Geschäftsanwendungen, die das Fortsetzen überstehen
Sie klappen den Laptop auf, und die Verbindungen der Geschäftsanwendung sind tot — die Ursache ist ein Entwurf, der Schlaf nicht vorsieht...
Wie Zwischenablage und Drag-and-Drop funktionieren — OLE-Datenübertragung in Geschäftsanwendungen richtig behandeln
Eine Excel-Tabelle fällt beim Einfügen auseinander, und nach dem Schließen der Quelle lässt sich nichts mehr einfügen: Ursache ist die Zw...
Verwandte Themen
Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.
Technische Windows-Themen
Portal zu Windows-Entwicklung, Fehleranalyse und der Nutzung bestehender Assets.
UI-Threading und Timer
WPF-/WinForms-UI-Thread, asynchrone Abläufe, Dispatcher und Timer-Entscheidungen.
Fehleranalyse und Langzeitprobleme
Sporadische Fehler, Kommunikationsdiagnose, Langzeitabstürze und Tests von Fehlerpfaden.
Leistungen zu diesem Thema
Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.
Windows-App-Entwicklung
Geschäftsanwendungen, Geräteintegration und Kommunikationstools von den Anforderungen bis zur Umsetzung.
Häufige Fragen
Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.
- Unter welchen Bedingungen erscheint „Keine Rückmeldung“?
- Das Betriebssystem beurteilt ein Fenster als ohne Rückmeldung, wenn eine App mit Fenster nicht auf Eingabe wartet, nicht in der Startverarbeitung ist und 5 Sekunden lang keine Nachricht abgeholt hat (PeekMessage). Das so beurteilte Fenster der obersten Ebene wird ausgeblendet und durch ein „Geisterfenster“ derselben Position, Größe und Erscheinung ersetzt. Der Titeltext „(Keine Rückmeldung)“ und das milchig-weiße Aussehen gehören zu diesem Geisterfenster, das nur Verschieben, Minimieren und Schließen erlaubt. Mit anderen Worten: „Keine Rückmeldung“ ist nichts, was die App selbst anzeigt — es ist ein Bildschirm, den das Betriebssystem an ihrer Stelle aufstellt.
- Gibt es eine Einstellung, damit „Keine Rückmeldung“ während laufender Arbeit nicht erscheint?
- Der Aufruf von DisableProcessWindowsGhosting deaktiviert den Tausch gegen ein Geisterfenster für diesen Prozess. Das macht den Hänger für den Benutzer nur weniger sichtbar — das Fenster reagiert trotzdem nicht auf Eingabe, und aus Benutzersicht ist es ein vollständiges Einfrieren ohne Weg zum Verschieben oder Schließen. Die eigentliche Lösung ist nicht, die Anzeige zu unterdrücken, sondern schwere Arbeit auf einen Worker-Thread zu verschieben, sodass der UI-Thread nie auch nur eine Zehntelsekunde blockiert ist, geschweige denn fünf. Beachten Sie außerdem, dass das Betriebssystem kein Geisterfenster anlegt, während ein Debugger verbunden ist, sodass es so aussehen kann, als geschähe „Keine Rückmeldung während des Debuggens nie“.
- Ist es akzeptabel, „Keine Rückmeldung“ mit DoEvents zu vermeiden (die Nachrichtenschleife manuell zu pumpen)?
- Es wird nicht empfohlen. DoEvents oder eine PeekMessage-Schleife mitten in schwerer Arbeit umgeht das Urteil ohne Rückmeldung, aber jeder Ereignishandler kann dann mitten in der Arbeit wieder eintreten — ein zweiter Klick auf die Schaltfläche, das Schließen des Fensters, ein Timer und so weiter. Wiedereintrittsfehler wie ein anderer Handler, der Daten umschreibt, die noch verarbeitet werden, oder ein Formular anfasst, das bereits geschlossen sein sollte, und eine Ausnahme wirft, sind zeitabhängig und schwer zu reproduzieren — und lästiger als „Keine Rückmeldung“ selbst. Der richtige Ansatz ist, die Arbeit selbst mit Task.Run oder Ähnlichem auf einen Worker-Thread zu verschieben und den UI-Thread nur für die Fortschrittsanzeige und das Annehmen von Abbruch verantwortlich zu lassen.
- Wie aktualisiere ich den Bildschirm (Steuerelemente) von einem Worker-Thread?
- WinForms-Steuerelemente und WPF-Elemente dürfen nur von dem Thread angefasst werden, der sie angelegt hat (normalerweise der UI-Thread). Sie direkt von einem Worker-Thread anzufassen verursacht Ausnahmen oder undefiniertes Verhalten. In C# ist async/await der einfachste Weg: Die Fortsetzung nach await kehrt zum aufrufenden UI-Thread zurück, sodass Sie Steuerelemente nach dem await normal aktualisieren können. Um ausdrücklich zu wechseln, nutzen Sie Control.Invoke/BeginInvoke in WinForms und Dispatcher.InvokeAsync in WPF. In nativem Win32 ist das etablierte Muster, dass der Worker-Thread dem UI-Thread mit PostMessage eine eigene Fertigstellungsnachricht sendet und die Fensterprozedur den Bildschirm aktualisiert.
- Wie untersuche ich die Ursache einer App, die „Keine Rückmeldung“ zeigt?
- Das Wichtige ist, den Zustand im hängenden Moment selbst festzuhalten. Nehmen Sie zuerst einen vollständigen Dump über die Registerkarte Details des Task-Managers mit „Abbilddatei erstellen“, und schauen Sie dann in WinDbg auf den Stack des UI-Threads (des Threads, der die Nachrichtenschleife ausführt). Ob er in synchronem I/O, einem Netzwarten, einem Sperrwarten oder dem Warten auf einen anderen Thread über SendMessage steckt, zeigt sich auf dem Stack so, wie es ist. Um einen lebenden Prozess anzusehen, nutzen Sie die Thread-Liste und die Stack-Ansicht von Process Explorer; um es über die Zeit zu verfolgen, erfassen Sie eine Aufzeichnung mit WPR. Siehe auch den einführenden WinDbg-Artikel dieser Site, Process Explorer in der Praxis und WPR/WPA in der Praxis.
Autorenprofil
Profilseite des Artikelautors.
Go Komura
Geschäftsführer von KomuraSoft LLC
Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.