Che cos'è davvero «Non risponde» — Come Windows decide che un'app è bloccata, e come progettare app che non lo sono
· Go Komura · Windows, Sviluppo Windows, Risoluzione dei problemi, Multithreading, WinForms, WPF, Win32 API, Progettazione UI
«L’app diventa bianca a metà operazione e mostra (Non risponde).» «Riceviamo ticket che si blocca occasionalmente, ma non si riproduce mai su una macchina di sviluppo.» — Per le app aziendali Windows, questo «Non risponde» è uno dei reclami più comuni. E ciò che è sorprendentemente poco noto è che chi mette su la visualizzazione «Non risponde» non è l’app bloccata stessa — è il sistema operativo.
Come fa Windows a sapere che un’app è «bloccata»? Cos’è quella finestra bianca smerigliata? Rivolto agli sviluppatori che scrivono app aziendali su Windows e al personale IT che prende ticket su app che si bloccano, questo articolo percorre il giudizio di «Non risponde» dalle basi del ciclo dei messaggi, e organizza le cause classiche di hang, i progetti che non si bloccano, e una procedura per indagare il momento del blocco — tutto fondato sulle fonti primarie.
1. Prima di tutto, la conclusione
- «Non risponde» è il giudizio del sistema operativo. Quando una finestra (e il thread GUI che la possiede) non è in attesa di input, non è nella sua sequenza di avvio, e non ha prelevato un messaggio (
PeekMessage) per 5 secondi, il sistema operativo la tratta come non rispondente. Il giudizio non è per processo.1 - La finestra imbiancata è una «finestra fantasma». Il sistema operativo ha nascosto la finestra originale e ha scambiato un falso della stessa posizione, dimensione e aspetto. Tutto ciò che potete fare è spostarla, ridurla a icona o chiuderla; i contenuti non stanno girando. Una finestra fantasma non viene creata mentre è attaccato un debugger.2
- La causa di un hang si riduce quasi sempre a una cosa sola. Il thread dell’UI che dovrebbe pompare il ciclo dei messaggi è bloccato su un lavoro pesante o su un’attesa. I/O sincrono, chiamate di rete, attese di lock, e
SendMessagetra thread sono i classici.3 - Il principio di progetto è «non aspettare e non calcolare sul thread dell’UI». Spostate il lavoro pesante su un thread worker (
async/await+Task.Runin C#) e lasciate il thread dell’UI dedicato a disegno, progresso e accettazione della cancellazione.4 DoEventse pompare manualmente il ciclo dei messaggi sono un terreno di coltura per bug di rientranza. La visualizzazione «Non risponde» sparisce, ma la struttura ora consente a eventi arbitrari di interrompere a metà lavoro. La separazione, non l’elusione, è l’approccio corretto.- L’indagine inizia catturando lo stato nel momento del blocco. Prendete un dump e guardate lo stack del thread dell’UI, e potete quasi sempre identificare su cosa sta aspettando.
2. Prerequisito: le app Windows sono guidate dai messaggi
Per capire «Non risponde», dovete prima interiorizzare che un’app GUI Windows è guidata dagli eventi. Un’app GUI non va a prendere l’input da sola; riceve messaggi che il sistema operativo consegna (mouse, tastiera, richieste di ridisegno, timer, e così via) e agisce su di essi.3
Ogni thread che crea una finestra ha una coda di messaggi e gira un ciclo dei messaggi come questo.
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg); // the window procedure is called
}
GetMessage preleva un messaggio dalla coda, e DispatchMessage chiama la procedura della finestra di quella finestra (la funzione di gestione dei messaggi). La gestione del clic di un pulsante, il ridisegno, e i gestori di eventi WinForms o WPF, quando li fate bollire, girano tutti dentro un’iterazione di questo ciclo.5
flowchart TB
accTitle: Struttura di base del ciclo dei messaggi
accDescr: Il sistema operativo mette mouse, tastiera e altri input nella coda dei messaggi del thread; il ciclo del thread dell'UI lo preleva con GetMessage, chiama la procedura della finestra con DispatchMessage, e torna in cima al ciclo quando l'elaborazione finisce
os["OS (input, richieste di ridisegno, timer)"] --> q["Coda dei messaggi del thread"]
q --> gm["Preleva con GetMessage"]
gm --> dm["DispatchMessage"]
dm --> wp["Gestisci nella procedura della finestra"]
wp --> gm
Figura 1: Il cuore di un’app GUI è il ciclo dei messaggi; ogni gestore di eventi gira come un’iterazione di questo ciclo.
Questa struttura ha una conseguenza importante. Se fate un lavoro che richiede tempo dentro la procedura della finestra (un gestore di eventi), il ciclo non può prelevare il messaggio successivo nel frattempo. Non può reagire né ai clic né alle richieste di ridisegno — è questo ciò che un «hang» è davvero.
Vale anche interiorizzare che i messaggi vengono consegnati per due percorsi. PostMessage mette il messaggio sulla coda e ritorna immediatamente, e il ciclo preleva ed elabora i messaggi in ordine. SendMessage, d’altra parte, chiama la procedura della finestra direttamente e non ritorna al chiamante finché l’elaborazione non è completa.36 Quella differenza entra dritta nella discussione sui deadlock del capitolo 4.
flowchart TB
accTitle: Due percorsi di consegna dei messaggi
accDescr: PostMessage mette il messaggio sulla coda e ritorna immediatamente; il ciclo dei messaggi preleva ed elabora i messaggi in ordine. SendMessage chiama la procedura della finestra direttamente e non ritorna al chiamante finché l'elaborazione non è completa
pm["PostMessage"] --> q2["Metti sulla coda (ritorna subito)"]
q2 --> loop["Il ciclo preleva ed elabora in ordine"]
sm["SendMessage"] --> direct["Chiama la procedura direttamente"]
direct --> w2["Non ritorna finché l'elaborazione non è completa"]
Figura 2: Anche se entrambi «inviano un messaggio», un Post in coda e un Send che aspetta il completamento hanno nature completamente diverse.
3. Come viene giudicato «Non risponde» — La regola dei 5 secondi e la finestra fantasma
Allora come fa il sistema operativo a sapere che «questa app si è bloccata»? Il criterio è documentato ufficialmente. Il sistema operativo tratta una finestra come non rispondente quando non è in attesa di input, non è nella sua sequenza di avvio, e non ha chiamato PeekMessage (prelievo di messaggio) per 5 secondi.1 In altre parole, il sistema operativo osserva se «il ciclo dei messaggi sta davvero girando» come prendereste un polso, e se non c’è polso per 5 secondi giudica la finestra non rispondente (la documentazione afferma che questo valore di 5 secondi può cambiare in futuro). L’unità di giudizio è la finestra e il thread GUI che la possiede; in un’app con diversi thread dell’UI, un thread bloccato non significa che le finestre su un altro thread siano morte. Il thread da guardare in un dump è il proprietario della finestra bloccata.
Cosa succede a una finestra di primo livello che è stata giudicata è documentato altrettanto. Il sistema operativo nasconde la finestra originale e la sostituisce con una «finestra fantasma» che ha lo stesso Z-order, posizione, dimensione e aspetto. Tutto ciò che l’utente può fare è spostarla, ridimensionarla o (forzatamente) chiuderla. L’app dentro non sta davvero rispondendo, quindi nessun’altra operazione funziona.2
flowchart TB
accTitle: Giudizio di finestra bloccata e scambio con la finestra fantasma
accDescr: Quando il thread dell'UI è bloccato su un lavoro pesante e il prelievo dei messaggi si ferma per 5 secondi, il sistema operativo giudica la finestra non rispondente, nasconde l'originale, scambia una finestra fantasma dello stesso aspetto, e offre all'utente solo spostare, ridurre a icona e chiudere
busy["Thread dell'UI bloccato su lavoro pesante"] --> stop["Il prelievo dei messaggi si ferma"]
stop --> judge{"5 secondi trascorsi?"}
judge -->|"no"| stop
judge -->|"sì"| ghost["Scambia una finestra fantasma"]
ghost --> u1["Il titolo mostra (Non risponde)"]
ghost --> u2["Bianco smerigliato; solo spostare e chiudere"]
Figura 3: Sia il testo «Non risponde» sia lo schermo bianco appartengono alla finestra fantasma che il sistema operativo ha scambiato, non all’app bloccata.
La stringa «(Non risponde)» che compare sulla barra del titolo, e l’aspetto bianco smerigliato sotto il tema Aero, appartengono entrambi a questa finestra fantasma. Ne seguono due conseguenze pratiche.
- Nel momento in cui «Non risponde» è visualizzato, il thread che possiede quella finestra non sta elaborando messaggi da almeno 5 secondi. Non è che «la visualizzazione è comparsa troppo presto» — il thread dell’UI è decisamente bloccato.
- Una finestra fantasma non viene creata mentre è attaccato un debugger.2 Quando sembra che «sotto il debugger non va mai in Non risponde, ma in release sì», l’hang stesso può essere lo stesso e solo la visualizzazione diversa.
C’è anche un’API, DisableProcessWindowsGhosting, che disabilita questo scambio per l’intero processo.7 È pensata per casi speciali come i terminali chiosco in cui non volete che il sistema operativo renda una finestra apparentemente operabile da sola. Chiamarla ferma la comparsa della visualizzazione «Non risponde», ma il fatto che l’app sia bloccata non cambia. Capite che questa non è qualcosa che un’app generale usa come contromisura a «Non risponde».
4. Perché le app si bloccano — Pattern classici che bloccano il thread dell’UI
La causa, fatta bollire, è un singolo punto — «il thread dell’UI non torna al ciclo dei messaggi» — ma le forme che incontrate in pratica cadono in alcuni classici.
flowchart TB
accTitle: Classificazione delle cause classiche che bloccano il thread dell'UI
accDescr: Le quattro famiglie classiche — I/O sincrono e chiamate di rete, attese di lock, SendMessage tra thread, e coinvolgimento COM STA — si riducono tutte allo stesso singolo punto che il thread dell'UI non può tornare al ciclo dei messaggi
kind{"Quale causa classica?"}
kind --> io{"I/O o un lock?"}
kind --> other{"SendMessage o COM?"}
io --> c1["I/O sincrono e rete"]
io --> c2["Attese di lock"]
other --> c3["SendMessage"]
c3 -.-> c3n["tra thread"]
other --> c4["Coinvolgimento COM STA"]
c1 --> core["L'UI non può tornare"]
c2 --> core
c3 --> core
c4 --> core
core --> ar["Controllo Non risponde"]
Figura 4: Il sintomo visibile è lo stesso, ma il colpevole che blocca il thread cade in quattro famiglie, e la contromisura differisce per ciascuna.
I/O sincrono e chiamate di rete. Questo è il più comune. Il pattern di fare in modo sincrono, dentro un gestore di clic di un pulsante, una lettura o scrittura di un file grande, una query a un database, una chiamata a una Web API, o l’accesso a un file su un’unità di rete. Su una macchina di sviluppo finisce in una frazione di secondo, quindi non ve ne accorgete; la latenza di rete in produzione o un singhiozzo del file server lo trasformano in un’attesa di decine di secondi, e ricevete ticket che «si blocca occasionalmente». Le unità di rete hanno timeout lunghi quando la connessione è giù, e peggiorano il sintomo in modo drammatico.
Attese di lock. Il pattern in cui il thread dell’UI tenta di prendere un lock su dati condivisi con un thread worker, e finisce per aspettare un worker che tiene quel lock a lungo. La disciplina dei lock è trattata in dettaglio nella serie pratica sul multithreading.
SendMessage tra thread. SendMessage non ritorna finché la procedura della finestra di destinazione non ha finito di elaborare.6 Quando lo inviate a una finestra su un altro thread, il mittente viene messo in attesa finché quel thread è in uno stato in cui può elaborare messaggi. Se il thread di destinazione sta a sua volta aspettando qualcosa, avete un deadlock da messaggi in cui ciascuna parte aspetta l’altra.3 Inviare a HWND_BROADCAST in particolare vi trascinerà dentro non appena una singola finestra non sta rispondendo. Quando non potete permettervi di aspettare, considerate SendMessageTimeout o PostMessage, che non aspetta una risposta.8
sequenceDiagram
accTitle: Deadlock causato da SendMessage tra thread
accDescr: Se un thread worker invia SendMessage a una finestra del thread dell'UI mentre il thread dell'UI è bloccato in attesa del risultato del worker, ciascuna parte aspetta che l'altra finisca e avete un deadlock
participant U as Thread dell'UI
participant W as Thread worker
U->>U: In attesa che il worker finisca (bloccato)
W->>U: SendMessage (non ritorna finché non è elaborato)
Note over U: Non può elaborare messaggi (bloccato)
Note over W: Non può tornare da SendMessage
Note over U,W: In attesa l'uno dell'altro — deadlock
Figura 5: «Il thread dell’UI aspetta il worker, e il worker aspetta il thread dell’UI via SendMessage» è un deadlock classico.
Coinvolgimento degli apartment COM. Le chiamate a un oggetto STA vengono consegnate come messaggi di finestra, quindi quando il thread dell’UI (STA) è bloccato, anche le chiamate COM da altri thread restano bloccate come danno collaterale. Quella struttura è spiegata nell’articolo su COM STA/MTA.
L’accumulo di «è solo un momento». Anche una chiamata sincrona da 50 ms, invocata 100 volte in un ciclo, è 5 secondi. La soglia di Non risponde è 5 secondi, ma la «lentezza» percepita inizia intorno ai 100 ms. Una regola pratica di progetto è «il thread dell’UI può essere bloccato solo per millisecondi».
5. Progetti che non si bloccano — Spostare il lavoro pesante fuori dal thread dell’UI
Il principio di progetto è una cosa sola: spostate il lavoro che richiede tempo fuori dal thread dell’UI. In C# (WinForms/WPF), async/await è l’approccio corretto più breve.
private async void RunButton_Click(object sender, EventArgs e)
{
runButton.Enabled = false;
try
{
// CPU-heavy work, or APIs that are only synchronous, go to a worker via Task.Run
var result = await Task.Run(() => HeavyCalculation(input));
// For I/O, use APIs that are natively async (they do not consume a thread either)
var data = await httpClient.GetStringAsync(url);
// After await you are back on the UI thread, so you can touch controls directly
resultLabel.Text = result + data.Length.ToString();
}
catch (Exception ex)
{
// An exception leaking from an async void handler will take the app down. Catch it here
MessageBox.Show($"The operation failed: {ex.Message}");
}
finally
{
runButton.Enabled = true;
}
}
Ci sono tre punti. Primo, mentre await sta aspettando, il thread dell’UI è di nuovo nel ciclo dei messaggi, quindi non andate in Non risponde. Secondo, la continuazione dopo await ritorna al thread dell’UI, quindi potete toccare i controlli normalmente dopo (toccare un controllo direttamente da un thread worker è vietato; se vi serve, usate Control.Invoke / Dispatcher.InvokeAsync).4 Terzo, disabilitate il pulsante mentre il lavoro gira, e per il resto uccidete la rientranza per progetto.
Il quadro è lo stesso in Win32 nativo: consegnate il lavoro a un thread worker, notificate il completamento al thread dell’UI come un messaggio personalizzato via PostMessage, e aggiornate l’UI nella procedura della finestra. PostMessage mette solo il messaggio sulla coda e ritorna immediatamente, quindi nemmeno il lato worker è bloccato.6 Scrivete l’attesa del thread worker stesso con la disciplina trattata nell’articolo sulle condition variable.
flowchart TB
accTitle: Divisione dei ruoli in un'app che non si blocca
accDescr: Il thread dell'UI è responsabile solo di accettare l'input, mostrare il progresso e accettare la cancellazione; un thread worker esegue il lavoro pesante e restituisce il completamento al thread dell'UI via PostMessage o una continuazione await
ui["Thread dell'UI: input, progresso, annulla"] -->|"Consegna il lavoro"| w["Thread worker: lavoro pesante"]
w -->|"PostMessage / continuazione await"| ui
ui -.-> ng["Niente I/O sincrono o calcoli lunghi sul thread dell'UI"]
Figura 6: Tenete il thread dell’UI come lo «sportello di accoglienza», consegnate sempre il lavoro pesante a un worker, e prendete solo la notifica di completamento.
Ciò che volete evitare è la tecnica di inserire Application.DoEvents() o un ciclo PeekMessage tra i pezzi di lavoro pesante solo per tenere viva la visualizzazione. Schivate Non risponde, ma gestori di eventi arbitrari rientrano in mezzo al lavoro. Un secondo clic del pulsante, la chiusura del form durante l’elaborazione, lo scatto di un timer — ognuno di essi può corrompere dati ancora in elaborazione, e i bug sono dipendenti dal timing e difficili da riprodurre. Tenete il pompaggio manuale del ciclo dei messaggi dentro una struttura limitata come una finestra di dialogo di progresso modale, e di regola risolvete con la separazione.
sequenceDiagram
accTitle: Linea temporale di un bug di rientranza causato da DoEvents
accDescr: Chiamare DoEvents in mezzo a un lavoro pesante consente al gestore di eventi di un clic in coda di interrompere e girare, riscrivere dati ancora in elaborazione, e poi riprendere il lavoro originale, producendo una corruzione dei dati dipendente dal timing
participant U as Thread dell'UI
U->>U: Inizia il lavoro pesante (dati in elaborazione)
U->>U: DoEvents (elabora i messaggi in coda)
Note over U: Il gestore del riclic del pulsante interrompe
U->>U: Il lavoro che interrompe riscrive i dati
U->>U: Il lavoro originale riprende (dati già incoerenti)
Figura 7: DoEvents cancella «Non risponde» in cambio di invitare eventi arbitrari in mezzo al lavoro.
Per un lavoro di lunga durata, includete nel progetto anche visualizzazione del progresso e cancellazione. Inviate il progresso all’UI con IProgress<T> e comunicate l’interruzione con un CancellationToken, e l’utente può vedere che «sta lavorando» e non arriverà a una terminazione forzata (che è spesso una causa di corruzione dei dati).
flowchart TB
accTitle: Flusso di progresso e cancellazione per un lavoro di lunga durata
accDescr: Il thread worker invia il progresso al thread dell'UI via IProgress; un'azione di annullamento sull'UI raggiunge il worker attraverso un CancellationToken; il worker si ferma a un confine conveniente e pulisce
w3["Worker: lavoro di lunga durata"] -->|"Progresso via IProgress"| ui2["UI: progresso e un pulsante Stop"]
ui2 -->|"CancellationToken"| w3
w3 --> stop2["Fermati a un confine e pulisci"]
Figura 8: Il progresso è «worker → UI»; la cancellazione è «UI → worker». Includete questo canale bidirezionale sottile nel progetto fin dall’inizio.
6. Indagare il momento del blocco
In un’indagine di «si blocca occasionalmente», la cosa più preziosa è lo stato dei thread nel momento esatto in cui è bloccata. Riavviate, e le prove sono andate.
Prendete un dump. Sulla scheda Dettagli di Gestione attività, clic destro sul processo di destinazione → «Crea file dump». Quello da solo vi dà un dump completo con lo stack di ogni thread. Già dire al personale IT che prende i ticket «quando si blocca, prendete questo prima di chiuderla» cambia moltissimo il tasso di successo dell’indagine. Per costruire un meccanismo di raccolta, vedere l’articolo sulla raccolta di crash dump.
Guardate lo stack del thread dell’UI. Aprite il dump in WinDbg e guardate lo stack del thread che sta pompando il ciclo dei messaggi (di solito il thread 0). L’I/O sincrono compare come ReadFile o un’API di rete, un’attesa di lock come una chiamata della famiglia WaitFor…, e SendMessage tra thread come un’attesa dentro SendMessage — così com’è. Come leggerlo è spiegato nell’articolo introduttivo su WinDbg.
Guardatelo live. Con Process Explorer potete ispezionare l’elenco dei thread e gli stack sul posto. Quando è costantemente lenta, prendete una traccia WPR e analizzate le attese del thread dell’UI nel tempo (WPR/WPA in pratica).
flowchart TB
accTitle: Procedura di base per indagare Non risponde
accDescr: Prendete un dump nel momento del blocco, guardate lo stack del thread dell'UI, identificate se è fermo su I/O sincrono, un'attesa di lock, o SendMessage tra thread, e collegatelo alla correzione di progetto corrispondente
hang["Il momento del blocco"] --> dump["Prendi un dump (prima di chiudere)"]
dump --> stack["Guarda lo stack del thread dell'UI"]
stack --> io["I/O sincrono o attesa di rete"]
stack --> lock["Attesa di lock"]
stack --> sm["SendMessage tra thread"]
io -.-> fix["Separa il sito su un worker"]
lock -.-> fix
sm -.-> fix
Figura 9: La protagonista dell’indagine è «un dump del momento del blocco»; lo stack del thread dell’UI è di per sé la classificazione della causa.
Potete anche standardizzare come fate un primo taglio dal sintomo. Se si blocca sempre su una particolare operazione, sospettate prima I/O sincrono dentro quel gestore. Se si blocca raramente e senza correlazione con un’operazione, sospettate l’ordine dei lock o un deadlock da SendMessage tra thread, e confrontate i bersagli di attesa di entrambi i thread nel dump. Se si blocca solo in un ambiente particolare, sospettate timeout da fattori ambientali come un’unità di rete, un proxy, o un software antivirus.
7. Riepilogo
- «Non risponde» è un meccanismo in cui il sistema operativo giudica che un’app non ha prelevato un messaggio per 5 secondi e scambia una finestra fantasma. Chi mette su la visualizzazione è il sistema operativo, non l’app.
- La causa di un hang è un singolo punto: «il thread dell’UI non può tornare al ciclo dei messaggi». I/O sincrono, la rete, le attese di lock, e SendMessage tra thread sono i classici.
- La contromisura è spostare il lavoro pesante fuori dal thread dell’UI. In C#,
async/await+Task.Run; in Win32, un thread worker +PostMessage. Prevenite la rientranza durante l’esecuzione per progetto, come disabilitare il pulsante. - Eluderlo con
DoEventsè in cambio di bug di rientranza.DisableProcessWindowsGhostingtoglie solo la visualizzazione. Nessuno dei due è una correzione della causa di fondo. - Per l’indagine, un dump del «momento del blocco» è la cosa più importante. La causa è quasi sempre scritta sullo stack del thread dell’UI così com’è.
Dal punto di vista dell’utente «Non risponde» è «è rotta», ma una volta che conoscete il meccanismo potete tradurlo nella frase precisa «il thread dell’UI non è tornato per 5 secondi». Lavorando a ritroso da quella frase, i candidati della causa, la correzione e la procedura di indagine cadono tutti in modo naturale.
Articoli correlati
- Sveglie spurie — Perché le condition variable si svegliano «senza essere notificate» e come attendere correttamente su Windows
- Best practice di multithreading in pratica: edizione .NET — Cosa decidere prima di aggiungere altri thread
- Fondamenti STA/MTA di COM - Modelli di threading e come evitare blocchi
- Leggere un crash dump con WinDbg + SOS — guida pratica all’analisi dopo la raccolta
- Process Explorer / Handle / VMMap in pratica — Inseguire hang, leak e «file in uso» dallo stato di adesso
- Lo spegnimento di Windows visto dalla tua app — Sopravvivere correttamente a notifiche di uscita, riavvii e interruzioni di corrente
Aree di consulenza correlate
KomuraSoft LLC si occupa di indagini sulle cause di app aziendali che «si bloccano occasionalmente» o vanno in «Non risponde» (analisi di dump e analisi di tracce), del refactoring di codice UI legacy pieno di lavoro sincrono verso async/await e la separazione su thread worker, e di revisioni di progetti UI che non si congelano. Anche quando non avete ancora una procedura di riproduzione, possiamo aiutare partendo dal progetto di come raccogliere le prove.
- Indagine sui difetti e analisi delle cause
- Consulenza tecnica e revisione della progettazione
- Sviluppo di app Windows
- Contattaci
Riferimenti
-
Microsoft Learn, IsHungAppWindow function (winuser.h). Sul criterio di giudizio per cui un’app è trattata come non rispondente quando «non è in attesa di input, non è nella sua sequenza di avvio, e non ha chiamato PeekMessage per il timeout interno di 5 secondi»; sul fatto che questo criterio di 5 secondi è soggetto a cambiamento; e sul fatto che la funzione restituisce sempre TRUE per una finestra fantasma. ↩ ↩2
-
Microsoft Learn, GetMessage function (winuser.h). Sul fatto che il sistema tratta una finestra di primo livello come non rispondente quando smette di rispondere ai messaggi per diversi secondi e la sostituisce con una finestra fantasma dello stesso Z-order, posizione, dimensione e aspetto; sul fatto che l’utente può solo spostarla, ridimensionarla o chiuderla; e sul fatto che una finestra fantasma non viene creata mentre è attaccato un debugger. ↩ ↩2 ↩3
-
Microsoft Learn, About Messages and Message Queues. Sul fatto che le app Windows sono guidate dagli eventi e la procedura della finestra elabora i messaggi; sulla distinzione tra messaggi in coda e messaggi inviati direttamente; sullo scambio di una finestra non rispondente con una finestra fantasma; e sulla sezione che tratta il deadlock da thread che si inviano messaggi l’un l’altro. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, How to make thread-safe calls to controls (Windows Forms). Sul fatto che i controlli WinForms non sono sicuri da toccare da un thread diverso da quello che li ha creati; sull’uso di Invoke/BeginInvoke per gli aggiornamenti da un altro thread; e sui pattern asincroni sicuri che usano async/await o BackgroundWorker. ↩ ↩2
-
Microsoft Learn, Using Messages and Message Queues. Su un’implementazione tipica del ciclo dei messaggi con GetMessage, TranslateMessage e DispatchMessage, e su come ispezionare una coda di messaggi. ↩
-
Microsoft Learn, SendMessage function (winuser.h). Sul fatto che SendMessage chiama la procedura della finestra specificata e non ritorna finché l’elaborazione non è completa; sul fatto che un invio a una finestra su un altro thread mette il mittente in attesa finché quel thread elabora il messaggio; e sulla differenza da PostMessage, che mette il messaggio sulla coda senza aspettare una risposta. ↩ ↩2 ↩3
-
Microsoft Learn, DisableProcessWindowsGhosting function (winuser.h). Sul fatto di poter disabilitare, per il processo GUI chiamante, la funzionalità di finestra fantasma che rende una finestra non rispondente riducibile a icona, spostabile e chiudibile; e sul fatto che la disabilitazione dura per la vita del processo. ↩
-
Microsoft Learn, SendMessageTimeout function (winuser.h). Sul fatto di poter inviare un messaggio con un timeout; e su un flag (SMTO_ABORTIFHUNG) che ritorna senza aspettare quando la finestra non sta rispondendo (è stata giudicata bloccata). ↩
Articoli correlati
Articoli recenti con gli stessi tag per approfondire argomenti vicini.
DllMain e il loader lock — Il vero motivo per cui vi dicono di «non fare niente nell'inizializzazione della DLL»
Perché non dovete chiamare LoadLibrary o sincronizzarvi con altri thread da DllMain. A partire dalle fonti primarie, l'articolo spiega co...
L'API thread pool Win32 — Concorrenza senza creare thread, tramite CreateThreadpoolWork
State spargendo chiamate CreateThread per tutto il codice nativo? Questo articolo spiega l'API thread pool Win32 ridisegnata in Vista — i...
App che si rompono alla ripresa dalla sospensione — Come funzionano gli eventi di alimentazione di Windows e come costruire app aziendali che li sopravvivono
Aprite il portatile e le connessioni dell'app aziendale sono morte — la causa è un progetto che non ha mai tenuto conto della sospensione...
Sveglie spurie — Perché le condition variable si svegliano «senza essere notificate» e come attendere correttamente su Windows
L'attesa di una condition variable può ritornare anche quando non è arrivata nessuna notifica (una sveglia spuria). L'articolo spiega, a ...
Come funzionano gli Appunti e il trascinamento della selezione — Gestire correttamente il trasferimento dati OLE nelle app aziendali
Incollate una tabella Excel e la formattazione si sfascia; chiudete l'app di origine e non potete più incollare — entrambi vengono dal fa...
Argomenti correlati
Queste pagine collocano l’argomento in un contesto più ampio di servizi e decisioni.
Argomenti tecnici Windows
Portale su sviluppo Windows, analisi dei problemi e valorizzazione delle risorse esistenti.
Thread UI e timer
Thread UI WPF / WinForms, flussi asincroni, Dispatcher e progettazione dei timer.
Servizi collegati all’argomento
L’articolo è direttamente collegato ai servizi seguenti.
Sviluppo di applicazioni Windows
Applicazioni aziendali, integrazione di dispositivi e strumenti di comunicazione, dai requisiti allo sviluppo.
Domande frequenti
Domande che ricorrono nelle consulenze sull’argomento dell’articolo.
- In quali condizioni compare «Non risponde»?
- Il sistema operativo giudica una finestra bloccata quando un'app con finestra non è in attesa di input, non è nella sua sequenza di avvio, e non ha prelevato un messaggio (PeekMessage) per 5 secondi. La finestra di primo livello bloccata viene nascosta e sostituita con una «finestra fantasma» della stessa posizione, dimensione e aspetto. Il testo della barra del titolo «(Non risponde)» e l'aspetto bianco smerigliato appartengono a questa finestra fantasma, che vi consente solo di spostare, ridurre a icona o chiudere. In altre parole, «Non risponde» non è qualcosa che l'app stessa visualizza — è una schermata che il sistema operativo mette su per conto dell'app.
- C'è un'impostazione per evitare che «Non risponde» compaia mentre il lavoro è in corso?
- Chiamare DisableProcessWindowsGhosting disabilita lo scambio con una finestra fantasma per quel processo. Questo però rende solo l'hang meno visibile all'utente — la finestra continua a non reagire all'input, e dal punto di vista dell'utente è un congelamento completo senza modo di spostare o chiudere. La vera correzione non è sopprimere la visualizzazione, ma spostare il lavoro pesante su un thread worker così che il thread dell'UI non sia mai bloccato nemmeno per un decimo di secondo, figuriamoci per cinque. Si noti anche che il sistema operativo non crea una finestra fantasma mentre è attaccato un debugger, quindi può sembrare che «Non risponde» non accada mai durante il debug.
- È accettabile evitare «Non risponde» con DoEvents (pompare manualmente il ciclo dei messaggi)?
- Non è raccomandato. Far girare DoEvents o un ciclo PeekMessage in mezzo a un lavoro pesante schiverà il giudizio di finestra bloccata, ma qualsiasi gestore di eventi può allora rientrare — un secondo clic del pulsante, la chiusura della finestra, un timer, e così via. Un altro gestore che riscrive dati ancora in elaborazione, o che tocca un form che doveva essere chiuso e lancia, produce bug di rientranza dipendenti dal timing e difficili da riprodurre — peggiori di «Non risponde» stesso. L'approccio corretto è spostare il lavoro stesso su un thread worker con Task.Run o simile, e lasciare al thread dell'UI solo la responsabilità della visualizzazione del progresso e dell'accettazione della cancellazione.
- Come aggiorno l'UI (i controlli) da un thread worker?
- I controlli WinForms e gli elementi WPF si possono toccare solo dal thread che li ha creati (normalmente il thread dell'UI). Toccarli direttamente da un thread worker provoca eccezioni o comportamento indefinito. In C#, async/await è il percorso più facile: la continuazione dopo await ritorna al thread dell'UI chiamante, quindi potete aggiornare i controlli normalmente dopo l'await. Per passare in modo esplicito, usate Control.Invoke/BeginInvoke in WinForms e Dispatcher.InvokeAsync in WPF. In Win32 nativo, il pattern consolidato è che il thread worker fa PostMessage di un messaggio di completamento personalizzato al thread dell'UI, e la procedura della finestra aggiorna l'UI.
- Come indago perché un'app sta mostrando «Non risponde»?
- La cosa importante è catturare lo stato nel «momento stesso» del blocco. Prima prendete un dump completo dalla scheda Dettagli di Gestione attività con «Crea file dump», poi in WinDbg guardate lo stack del thread dell'UI (il thread che gira il ciclo dei messaggi). Se è fermo in I/O sincrono, in un'attesa di rete, in un'attesa di lock, o in attesa di un altro thread via SendMessage, compare sullo stack così com'è. Per guardare un processo live, l'elenco dei thread e la vista stack di Process Explorer sono utili; per seguirlo nel tempo, catturare una traccia WPR è efficace. Vedere anche l'articolo introduttivo su WinDbg di questo sito, Process Explorer in pratica, e WPR/WPA in pratica.
Profilo dell’autore
Pagina di presentazione dell’autore dell’articolo.
Go Komura
Rappresentante di KomuraSoft LLC
Specializzato nello sviluppo di software Windows, nella consulenza tecnica e nell’analisi dei malfunzionamenti, soprattutto nei progetti con sistemi esistenti e guasti difficili da riprodurre.