Qué es realmente «No responde» — Cómo decide Windows que una aplicación se ha colgado, y cómo diseñar aplicaciones que no lo hagan
· Actualizado el: · Go Komura · Windows, Desarrollo en Windows, Investigación de fallos, Multithreading, WinForms, WPF, Win32 API, Diseño de IU
Historial de revisiones (primera versión, publicada el 22 Aug 2026)
- Primera publicación
Citar este artículo(DOI (archivo registrado): 10.5281/zenodo.22176663)
Los DOI siguientes remiten a versiones ya archivadas y pueden diferir del texto actual. Para citar el texto actual, utilice la URL de esta página.
Go Komura (2026). Qué es realmente «No responde» — Cómo decide Windows que una aplicación se ha colgado, y cómo diseñar aplicaciones que no lo hagan. KomuraSoft LLC. https://comcomponent.com/es/blog/windows-app-not-responding-hang-mechanism/
- DOI (archivo registrado)
- 10.5281/zenodo.22176663
- DOI (última versión registrada)
- 10.5281/zenodo.22176664
«Durante el procesamiento, la pantalla se pone blanca y la barra de título dice “No responde”.» «Los usuarios dicen que se cuelga de vez en cuando, pero nunca se reproduce en una máquina de desarrollo.» Son quejas habituales sobre las aplicaciones empresariales de Windows.
Lo primero que hay que retener es que quien muestra «No responde» no es la propia aplicación, sino Windows. Windows detecta que el procesamiento de mensajes de la ventana se ha detenido y sustituye la pantalla original por una ventana de reemplazo. La clave para rastrear la causa es qué está haciendo el hilo de IU que posee esa ventana, de modo que no puede procesar el siguiente mensaje.12
Este artículo se dirige a desarrolladores que crean aplicaciones empresariales en Windows y al personal de TI que recibe consultas sobre aplicaciones que se cuelgan. Procede en el orden juicio del sistema operativo → bucle de mensajes → acotar la causa por categoría → diseños que no se cuelgan → procedimiento de investigación. Si necesita investigar una aplicación que está colgada ahora mismo, lea primero el capítulo 7.
1. La conclusión primero: no ocultar la visualización, dejar libre el hilo de IU
El mecanismo de «No responde», cómo corregirlo y cómo investigarlo se ven con más claridad si se separan así.
| Lo que quiere saber o resolver | Lo primero que hay que retener | Explicación detallada |
|---|---|---|
| ¿Qué mira Windows para juzgar que no responde? | Mira la ventana y el procesamiento de mensajes del hilo GUI que la posee. El juicio no es a escala del proceso | Capítulo 2 |
| ¿Por qué se detienen los botones y el repintado? | El hilo de IU no vuelve de un controlador de eventos o de una espera, así que no puede recuperar el siguiente mensaje | Capítulos 3 y 4 |
| No quiero que la pantalla se congele durante un trabajo largo | Mover el cálculo de CPU y las API solo síncronas a un trabajador; usar API asíncronas para la E/S | Capítulo 5 |
| ¿Se puede evitar solo la visualización con DoEvents o un ajuste? | Invita errores de reentrada o solo oculta la visualización. No es una corrección de causa | Capítulo 6 |
| Averiguar la causa de un cuelgue ocasional | Tomar un volcado en el momento del cuelgue, antes de salir o reiniciar | Capítulo 7 |
El principio de la contramedida es no esperar mucho ni calcular de forma pesada en el hilo de IU. El hilo de IU se encarga de la entrada, el dibujo, la visualización de progreso y la aceptación de la cancelación, y se separa del trabajo que lleva tiempo.3
Además, el tiempo hasta que aparece «No responde» y el tiempo de respuesta cómodo de usar son cosas distintas. Quedarse por debajo de 5 segundos no basta. La sección 4.5 explica esta diferencia.
2. «No responde» es el juicio del sistema operativo: la regla de 5 segundos y la pantalla de reemplazo
2.1 La unidad de juicio no es el proceso entero, sino la ventana y el hilo propietario
En la descripción de Microsoft de IsHungAppWindow, una ventana se considera que no responde cuando cumple las condiciones siguientes.1
| Condición | Contenido |
|---|---|
| No espera entrada | No está en un estado de espera de entrada |
| No está en el procesamiento de arranque | La aplicación no está en su procesamiento de arranque |
| No recupera mensajes | No ha llamado a PeekMessage durante el tiempo de espera interno de 5 segundos |
El sistema operativo no mira qué está calculando la aplicación, sino si el bucle de mensajes está girando. El bucle de mensajes es el mecanismo que recupera en orden las peticiones de entrada y de repintado y las procesa. El capítulo 3 explica el funcionamiento concreto.
La misma documentación indica también que el valor de 5 segundos puede cambiar en el futuro. Es el tiempo de espera interno del juicio de no respuesta, no un criterio de diseño que diga «puede detener la IU hasta 5 segundos».1
El juicio no es por proceso. En una aplicación con varios hilos de IU, una ventana puede estar colgada mientras las ventanas que pertenecen a otros hilos siguen funcionando. En la investigación también hay que ir más allá del proceso y identificar el hilo que posee la ventana colgada.
2.2 La pantalla blanca esmerilada es una «ventana fantasma»
Cuando una ventana de nivel superior se juzga que no responde, Windows oculta la ventana original y la sustituye por una ventana fantasma con el mismo orden Z, posición, tamaño y aspecto. El «(No responde)» del título y el aspecto blanco esmerilado bajo el tema Aero vienen de esta pantalla de reemplazo, no de la aplicación colgada.2
| Estado de la pantalla | Lo que ve el usuario |
|---|---|
| La ventana original de la aplicación | No puede procesar mensajes; no reacciona a clics ni al repintado |
| La ventana fantasma que proporciona el sistema operativo | Operaciones limitadas: mover, cambiar de tamaño, minimizar y cerrar |
Poder mover la ventana de reemplazo no significa que el contenido de la aplicación haya vuelto a funcionar. El sistema operativo solo asume las operaciones mínimas.24
Una queja de que «la visualización aparece demasiado pronto» también se trata primero como un problema de que el hilo de IU no vuelve al procesamiento de mensajes durante mucho tiempo. Investigar el procesamiento detenido va antes de suprimir la visualización.
2.3 Que no aparezca bajo el depurador no significa que no esté colgada
Mientras hay un depurador acoplado, el sistema operativo no crea ventanas fantasma. Por eso, una diferencia del tipo «no aparece al depurar, pero pasa a No responde en la ejecución normal» puede significar que el hilo de IU está bloqueado exactamente igual y solo cambia la visualización.2
También hay una API que deshabilita el intercambio por ventana fantasma a escala de proceso, pero no es una API que corrija el cuelgue en sí. La sección 6.2 separa el uso previsto.
3. Por qué se detiene la pantalla: el hilo de IU y el bucle de mensajes
3.1 La entrada y el repintado los procesa el mismo hilo
Las aplicaciones GUI de Windows están dirigidas por eventos. Reciben mensajes del ratón, el teclado, peticiones de repintado, temporizadores, etc., y ejecutan el procesamiento correspondiente a cada uno. Cada hilo que crea una ventana tiene una cola de mensajes y ejecuta un bucle como el siguiente.5
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg); // se llama al procedimiento de ventana
}
GetMessage recupera un mensaje de la cola, y DispatchMessage llama al procedimiento de ventana de la ventana. El procedimiento de ventana es la función que ejecuta el procesamiento correspondiente a un mensaje. El manejo del clic de un botón, el repintado y los controladores de eventos de WinForms y WPF se conectan todos a este procesamiento de mensajes en el hilo de IU.6
flowchart TB
accTitle: Estructura básica del bucle de mensajes
accDescr: El sistema operativo coloca la entrada de ratón, teclado y demás en la cola de mensajes del hilo; el bucle del hilo de IU la recupera con GetMessage, llama al procedimiento de ventana con DispatchMessage y vuelve al principio del bucle cuando termina el procesamiento
os["SO (entrada, peticiones de repintado, temporizadores)"] --> q["Cola de mensajes del hilo"]
q --> gm["Recuperar con GetMessage"]
gm --> dm["DispatchMessage"]
dm --> wp["Procesar en el procedimiento de ventana"]
wp --> gm
Figura 1: Recuperar un mensaje, procesarlo en el procedimiento de ventana y volver al bucle. Este ciclo sostiene la entrada y el dibujo.
¿Qué ocurre entonces si desde un controlador de clic se llama a un procesamiento largo? Hasta que ese procesamiento no vuelva, el hilo de IU no puede recuperar el siguiente mensaje. Los clics y las peticiones de repintado que llegan después ya no se pueden procesar. Esa es la estructura básica de una «pantalla congelada».
3.2 PostMessage y SendMessage vuelven en momentos distintos
La entrega de mensajes tiene dos caminos: uno que coloca el mensaje en la cola y otro que espera a que termine el procesamiento.57
| API | Comportamiento | Cuándo vuelve el llamador |
|---|---|---|
PostMessage |
Coloca el mensaje en la cola; el receptor lo recupera y lo procesa | Vuelve una vez encolado el mensaje. No espera a que termine el procesamiento |
SendMessage |
Envía el mensaje al procedimiento de ventana y lo hace ejecutar | No vuelve hasta que el procedimiento de ventana termina |
En particular, cuando se envía SendMessage a una ventana de otro hilo, el emisor también queda a la espera hasta que el receptor pueda procesar el mensaje. Esta diferencia conduce de forma directa al interbloqueo de la sección 4.3.7
4. Causas por categoría: cinco patrones que taponan el hilo de IU
El aspecto de «No responde» es el mismo, pero lo que taponan es distinto. Separe primero los candidatos y confírmelos al final con los volcados y trazas del capítulo 7.
| Causa candidata | Lo que ocurre en el hilo de IU | Aparición típica |
|---|---|---|
| E/S síncrona o llamada de red | Espera a que terminen un archivo, una base de datos, una API web, etc. | Rápido en la máquina de desarrollo, pero se cuelga en producción o en un entorno concreto |
| Espera de bloqueo | No puede adquirir un bloqueo que sostiene un trabajador y espera | Se cuelga de forma rara, según cómo se solapen los procesamientos |
SendMessage entre hilos |
Espera a que otro hilo procese el mensaje | Queda atrapado en una espera mutua o en un broadcast |
| Llamada a un STA de COM | Espera el procesamiento de mensajes del STA de destino | La parada del hilo de IU se propaga a las llamadas COM de otros hilos |
| Encadenamiento de procesamientos cortos | Cada uno es corto, pero la ejecución seguida no vuelve al bucle | La operación solo se vuelve pesada cuando crece el número de elementos |
4.1 E/S síncrona y llamadas de red
El patrón más habitual es, en el controlador de clic de un botón, hacer de forma síncrona la lectura o escritura de un archivo grande, una consulta a base de datos, una API web o el acceso a una unidad de red.
Lo que termina en una fracción de segundo en la máquina de desarrollo puede convertirse en una espera de varias decenas de segundos con la latencia de red de producción o un problema del servidor de archivos. Las unidades de red tienen tiempos de espera largos cuando se corta la conexión, lo que agrava el síntoma. «En la máquina de desarrollo era rápido» no es motivo para esperar en el hilo de IU.
4.2 El hilo de IU espera un bloqueo que sostiene un trabajador
Los bloqueos que protegen datos compartidos también pueden detener la IU. Si un trabajador sostiene un bloqueo durante mucho tiempo y el hilo de IU también intenta tomarlo, el hilo de IU espera hasta poder adquirirlo.
Aunque mueva el trabajo a un trabajador, la pantalla no se libera si el hilo de IU espera a que termine ese trabajo o a que se libere el bloqueo. La disciplina de los bloqueos se trata con detalle en la serie práctica de multithreading.
4.3 Esperar mutuamente la finalización con SendMessage
El interbloqueo clásico es la combinación en la que el hilo de IU espera a que termine un trabajador, y ese trabajador envía SendMessage a la IU y espera. La IU está en espera y no puede procesar mensajes, y el trabajador no puede volver de SendMessage, así que ninguno de los dos avanza.75
sequenceDiagram
accTitle: Interbloqueo por SendMessage entre hilos
accDescr: Cuando un hilo trabajador envía SendMessage a la ventana del hilo de IU mientras este está bloqueado esperando el resultado del trabajador, cada uno espera a que el otro termine y el resultado es un interbloqueo
participant U as Hilo de IU
participant W as Hilo trabajador
U->>U: Esperar a que termine el trabajador (bloqueado)
W->>U: SendMessage (no vuelve hasta que se procese)
Note over U: No puede procesar mensajes (está esperando)
Note over W: No puede volver de SendMessage
Note over U,W: Espera mutua: interbloqueo
Figura 2: Separar el trabajo hacia un trabajador no basta. Si la IU y el trabajador esperan cada uno a que el otro termine, se produce un interbloqueo.
El envío a HWND_BROADCAST también exige cuidado. Basta una sola ventana que no responda para arrastrar al emisor. Cuando no puede seguir esperando, considere SendMessageTimeout, que pone un tiempo de espera, o PostMessage, que no espera a que termine el procesamiento.8
4.4 Un STA de COM se detiene y arrastra a otros hilos
Las llamadas desde otros hilos a un objeto STA se entregan como mensajes de ventana. Por eso, cuando el hilo de IU (un STA) está taponado, las llamadas COM hacia él también se bloquean. Es una estructura en la que no solo la IU, sino también los llamadores, quedan en espera.
La relación entre apartamentos y procesamiento de mensajes se explica en el artículo de COM STA/MTA.
4.5 Repetir «una vez es un instante» 100 veces
Aunque un procesamiento síncrono de 50 ms, repetido 100 veces, suma 5 segundos. No juzgue midiendo cada función por separado y declarándola corta; piense en el tiempo total hasta que el hilo de IU vuelve al bucle de mensajes.
La sensación de lentitud empieza hacia los 100 ms. No tome como objetivo los 5 segundos del juicio de no respuesta; diseñe asumiendo que el hilo de IU solo puede taponarse en milisegundos.
5. Diseños que no se cuelgan: separar la ejecución del trabajo y la actualización de la pantalla
5.1 Separar el cálculo y las API síncronas de la E/S asíncrona
El principio de la contramedida es sacar del hilo de IU el trabajo que lleva tiempo. No todo se mueve, sin embargo, del mismo modo.
| Tipo de trabajo | Tratamiento básico en C# | Papel del hilo de IU |
|---|---|---|
| Cálculo con mucha carga de CPU | Moverlo a un hilo trabajador con Task.Run |
Esperar la finalización de forma asíncrona y mostrar el resultado |
| Procesamiento que solo tiene una API síncrona | Separarlo a un hilo trabajador | No taponar la IU esperando la finalización de forma síncrona |
| E/S con una API asíncrona | Hacer await de GetStringAsync o similar |
Volver al procesamiento de mensajes mientras las E/S están pendientes |
| Entrada, dibujo, progreso, cancelación | Encargarse en el hilo de IU | No mezclar un cálculo largo ni E/S síncrona |
Con E/S asíncrona no hace falta ocupar un hilo solo para esperar. El ejemplo de código de más abajo también distingue: Task.Run para el cálculo y la API asíncrona nativa para la comunicación HTTP.3
5.2 En C#, volver a la IU con async/await y actualizarla
El ejemplo siguiente separa, en un controlador de clic de WinForms, el trabajo pesado y la actualización de la IU. Como este ejemplo parte de un controlador de eventos de IU y conserva el contexto de ejecución de la IU, la continuación después de await vuelve al hilo de IU y puede actualizar los controles.3
private async void RunButton_Click(object sender, EventArgs e)
{
runButton.Enabled = false;
try
{
// Cálculo con mucha carga de CPU y API solo síncronas: al trabajador con Task.Run
var result = await Task.Run(() => HeavyCalculation(input));
// Para E/S, usar una API nativamente asíncrona (tampoco consume un hilo)
var data = await httpClient.GetStringAsync(url);
// Tras await estamos de vuelta en el hilo de IU, se puede tocar los controles de forma directa
resultLabel.Text = result + data.Length.ToString();
}
catch (Exception ex)
{
// Una excepción que se escapa de un controlador async void tumba la aplicación. Capturarla aquí
MessageBox.Show($"El procesamiento ha fallado: {ex.Message}");
}
finally
{
runButton.Enabled = true;
}
}
Hay cuatro puntos que retener.
| Lugar | Intención |
|---|---|
Esperar la finalización con await |
Devolver el hilo de IU al bucle de mensajes cuando surge una espera |
Actualizar la pantalla después de await |
No tocar los controles desde el trabajador |
| Deshabilitar el botón durante la ejecución | Impide que el mismo procesamiento se inicie varias veces |
catch y finally |
No dejar escapar excepciones del controlador async void y restaurar el estado del botón |
Es un ejemplo de código que muestra el reparto de papeles; la implementación de HeavyCalculation y similares, el trato al cierre del formulario, y el progreso y la cancelación se omiten. El progreso y la interrupción que necesita un trabajo largo se incorporan aparte, como en la sección 5.4.
Los controles de WinForms y los elementos de WPF se tratan desde el hilo que los creó. Tocarlos de forma directa desde un trabajador lleva a excepciones o a comportamiento indefinido. Para volver de forma explícita al hilo de IU, use Control.Invoke / BeginInvoke en WinForms y Dispatcher.InvokeAsync en WPF.3
5.3 En Win32, notificar la finalización con PostMessage
El reparto de papeles es el mismo en Win32 nativo. Pase el trabajo a un trabajador y, al terminar, envíe con PostMessage un mensaje de finalización personalizado y actualice la pantalla en el procedimiento de ventana del hilo de IU. Como PostMessage vuelve una vez encolado el mensaje, el trabajador no espera a que termine el procesamiento de la IU.7
flowchart TB
accTitle: Reparto de papeles de una aplicación que no se cuelga
accDescr: El hilo de IU solo se encarga de la entrada, la visualización de progreso y la aceptación de la cancelación; un hilo trabajador ejecuta el trabajo pesado y devuelve la finalización al hilo de IU mediante PostMessage o una continuación await
ui["Hilo de IU: entrada, progreso, cancelación"] -->|"pasar el trabajo"| w["Hilo trabajador: trabajo pesado"]
w -->|"PostMessage / continuación await"| ui
ui -.-> ng["Prohibido E/S síncrona o cálculo largo en el hilo de IU"]
Figura 3: Pasar el cálculo pesado y el procesamiento síncrono al trabajador, y devolver las actualizaciones de pantalla al hilo de IU. La E/S asíncrona se espera con una API asíncrona, como en la sección 5.1.
La espera del trabajador en sí se escribe según la disciplina tratada en el artículo de variables de condición. También es importante que el hilo de IU no vuelva a esperar al trabajador de forma síncrona.
5.4 Diseñar desde el principio el progreso y la cancelación
Aunque la pantalla no se congele, si no cambia nada durante mucho tiempo el usuario no puede saber si sigue trabajando. Por eso diseñe la visualización de progreso y la aceptación de la cancelación como parte del procesamiento.
| Sentido de la comunicación | Mecanismo | Papel |
|---|---|---|
| Trabajador → IU | IProgress<T> |
Comunicar el avance a la pantalla |
| IU → trabajador | CancellationToken |
Transmitir la petición de detenerse |
El trabajador se interrumpe en un punto de corte razonable y limpia. Disponer de medios de progreso y de cancelación reduce las situaciones en las que el usuario recurre a un cierre forzado. El cierre forzado puede provocar corrupción de datos.
6. Dos métodos que parecen atajos, y sus límites
6.1 DoEvents invita a otros eventos a mitad del procesamiento
Si inserta Application.DoEvents() o un bucle PeekMessage durante un trabajo pesado, se procesan mensajes y se evita la visualización de «No responde». Sin embargo, otro controlador de eventos se ejecuta entonces mientras el procesamiento original aún no ha terminado. Eso es reentrada.
Por ejemplo, se llama a DoEvents a mitad de transformar datos y se procesa un segundo clic del botón que estaba en cola. Ese controlador reescribe los mismos datos y luego el procesamiento original se reanuda. En ese orden, el estado que el procesamiento original daba por supuesto ya se ha perdido.
No solo reentran los botones. También entran el cierre del formulario y los temporizadores. Errores como datos en vuelo rotos o una excepción al tocar un formulario cerrado dependen del momento, se reproducen mal y suelen ser más difíciles de investigar que «No responde».
El principio es separar el trabajo, no bombear el bucle a mano. El procesamiento manual de mensajes se limita a estructuras acotadas, como un cuadro de diálogo de progreso modal. Para un trabajo largo habitual, use la separación y la prevención de reentrada del capítulo 5.
6.2 Deshabilitar las ventanas fantasma no devuelve el control
DisableProcessWindowsGhosting es una API que deshabilita el intercambio por ventana fantasma para el proceso que llama. La deshabilitación dura durante la vida del proceso.4
Existe para usos especiales, como un terminal quiosco, donde se quiere evitar que el sistema operativo ponga una ventana de reemplazo operable. El hecho de que el procesamiento de mensajes se haya detenido no cambia, y el usuario pierde también el medio de mover o salir que ofrecía la ventana fantasma. No es algo que se use como contramedida de «No responde» en una aplicación ordinaria.
| Método | Lo que cambia | Lo que queda |
|---|---|---|
Bombear a mano con DoEvents o similar |
Procesar otros mensajes a mitad del trabajo | Eventos arbitrarios reentran y pueden romper el estado |
DisableProcessWindowsGhosting |
Impide que el sistema operativo sustituya una ventana de reemplazo | El hilo de IU sigue taponado |
| Separar el trabajo largo de la IU | Permite que el hilo de IU vuelva a la entrada y al dibujo | También hay que diseñar el progreso, la cancelación y la prevención de reentrada |
7. Procedimiento de investigación: conservar el momento del cuelgue antes de cerrar
7.1 Tomar primero un volcado
En la investigación de «se cuelga de vez en cuando», lo más valioso es el estado de los hilos en el momento mismo del cuelgue. En cuanto se sale o se reinicia, ese estado se pierde.
En la pestaña «Detalles» del Administrador de tareas, haga clic con el botón derecho en el proceso de destino y elija «Crear archivo de volcado». Obtiene un volcado completo que incluye las pilas de todos los hilos. Comparta también el procedimiento con quienes reciben las consultas: «si se cuelga, tome un volcado antes de cerrar».
Para construir el mecanismo de recolección, consulte el artículo de recolección de volcados de memoria.
7.2 Mirar el hilo que posee la ventana colgada
Abra el volcado en WinDbg y compruebe la pila del hilo de IU. El hilo que hace girar el bucle de mensajes suele ser el hilo 0, pero hay aplicaciones con varios hilos de IU, así que no decida solo por el número; mire el hilo que posee la ventana colgada.
| Lo que aparece en la pila | Qué examinar a continuación |
|---|---|
Espera en ReadFile o en una API de red |
Acceso a archivos, E/S síncrona, espera de una respuesta de red |
Espera en una función WaitFor… |
El bloqueo o el objeto de sincronización. A qué finalización espera y qué está haciendo la otra parte |
Espera dentro de SendMessage |
Si el hilo de destino puede procesar mensajes. Si se esperan mutuamente |
En la pila del hilo de IU aparece en qué está esperando. Si se sospecha un interbloqueo, empareje los destinos de espera de ambos hilos, no de uno solo. Cómo leerlas se explica en el artículo introductorio de WinDbg.
flowchart TB
accTitle: Procedimiento básico para investigar No responde
accDescr: Tomar un volcado en el momento del cuelgue, mirar la pila del hilo de IU, identificar si está detenido en E/S síncrona, una espera de bloqueo o SendMessage entre hilos, y conectar eso con la corrección de diseño correspondiente
hang["El momento del cuelgue"] --> dump["Tomar un volcado (antes de cerrar)"]
dump --> stack["Mirar la pila del hilo de IU"]
stack --> io["E/S síncrona o espera de red"]
stack --> lock["Espera de bloqueo"]
stack --> sm["SendMessage entre hilos"]
io -.-> fix["Separar el punto afectado hacia un trabajador"]
lock -.-> fix
sm -.-> fix
Figura 4: Desde el volcado del momento del cuelgue, seguir a qué espera el hilo de IU. Si es E/S, asincronizarla o separarla; si es un bloqueo o SendMessage, comprobar también la estructura de espera.
7.3 Distinguir la inspección en vivo y el análisis en el tiempo
Además de los volcados, hay una forma de mirar un proceso en ejecución en el momento y una forma de registrar el transcurso del tiempo.
| Lo que quiere averiguar | Método |
|---|---|
| Conservar el momento del cuelgue y examinarlo después | Tomar un volcado y analizarlo en WinDbg |
| Ver en el momento los hilos y las pilas de un proceso vivo | Usar Process Explorer |
| Seguir en el tiempo una lentitud constante o las esperas del hilo de IU | Capturar una traza con WPR y analizarla en WPA |
Cómo tomar una serie temporal se trata en WPR/WPA en la práctica.
7.4 Acotar candidatos a partir del síntoma y confirmarlos con pruebas
Las condiciones de reproducción también son una entrada a la investigación. No decida la causa solo por el síntoma; crúcela con volcados y trazas.
| Síntoma | Sospechar primero | Dónde comprobar |
|---|---|---|
| Siempre se cuelga en una operación concreta | E/S síncrona o similar dentro de ese controlador | El procesamiento de esa operación y la pila del hilo de IU |
| Se cuelga de forma rara, sin correlación con una operación | Orden de bloqueos, o un interbloqueo de SendMessage entre hilos |
Las pilas de ambos hilos que se esperan mutuamente |
| Solo se cuelga en un entorno concreto | Esperas y tiempos de espera por unidades de red, proxy, antivirus, etc. | La API en espera y su tiempo de respuesta en ese entorno |
8. Resumen
«No responde» es un mecanismo en el que Windows juzga que el procesamiento de mensajes de una ventana se ha detenido y pone una pantalla de reemplazo. La unidad de juicio es la ventana y el hilo GUI que la posee, no el proceso entero. El tiempo de espera interno de 5 segundos es un valor que puede cambiar en el futuro y se distingue del tiempo de respuesta cómodo de una IU.12
Lo que hay que corregir no es la visualización, sino el cálculo o la espera que ocupa el hilo de IU durante mucho tiempo. El trabajo de CPU y las API solo síncronas van al trabajador, la E/S asíncrona a API asíncronas, y las actualizaciones de pantalla vuelven al hilo de IU. Diseñe juntos el progreso, la cancelación y la prevención de reentrada. Evadirlo con DoEvents o deshabilitar las ventanas fantasma no es un sustituto.
Cuando la causa es desconocida, empiece por tomar un volcado antes de salir y mirar a qué espera el hilo que posee la ventana colgada. Sustituya «parece roto» por «el hilo de IU no puede volver al procesamiento de mensajes», y los lugares que investigar y las correcciones de diseño se conectan.
Artículos relacionados
- Despertares espurios — Por qué las variables de condición despiertan «sin haber sido notificadas» y cómo esperar correctamente en Windows
- Buenas prácticas de multithreading en la práctica — Edición .NET: qué decidir antes de aumentar los hilos
- Conocimientos básicos de STA/MTA en COM - El modelo de subprocesos y cómo evitar los bloqueos
- WinDbg + SOS para leer volcados de memoria — introducción práctica al análisis tras la recolección
- Process Explorer / Handle / VMMap en la práctica ── cómo rastrear cuelgues, fugas y «archivo en uso» desde el estado actual
- El apagado de Windows visto desde la aplicación ── cómo sobrevivir correctamente a la notificación de cierre, el reinicio y el corte de energía
Áreas de consultoría relacionadas
En KomuraSoft LLC nos encargamos de investigaciones de causa (análisis de volcados y análisis de trazas) de aplicaciones empresariales que «se cuelgan de vez en cuando» o pasan a «No responde», de refactorizar código de IU legado lleno de procesamiento síncrono hacia async/await y la separación en hilos trabajadores, y de revisiones de diseños de IU que no se congelan. Incluso cuando el procedimiento de reproducción aún no se conoce, podemos ayudar empezando por el diseño de cómo recoger las pruebas.
- Investigación de fallos y análisis de causas
- Consultoría técnica y revisión de diseño
- Desarrollo de aplicaciones Windows
- Contacto
Referencias
-
Microsoft Learn, IsHungAppWindow function (winuser.h). Sobre el criterio de juicio de que una aplicación se trata como que no responde cuando «no está esperando entrada, no está en su procesamiento de arranque y no ha llamado a PeekMessage durante el tiempo de espera interno de 5 segundos»; sobre que este criterio de 5 segundos está sujeto a cambio; y sobre que la función siempre devuelve TRUE para una ventana fantasma. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, GetMessage function (winuser.h). Sobre que el sistema trata una ventana de nivel superior como que no responde cuando deja de responder a los mensajes durante varios segundos y la sustituye por una ventana fantasma del mismo orden Z, posición, tamaño y aspecto; sobre que el usuario solo puede moverla, cambiar su tamaño o cerrarla; y sobre que no se crea una ventana fantasma mientras hay un depurador acoplado. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, How to make thread-safe calls to controls (Windows Forms). Sobre que los controles de WinForms no son seguros de tocar desde ningún hilo distinto del que los creó; sobre usar Invoke/BeginInvoke para actualizaciones desde otro hilo; y sobre patrones asíncronos seguros usando async/await o BackgroundWorker. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, DisableProcessWindowsGhosting function (winuser.h). Sobre poder deshabilitar, para el proceso GUI que llama, la característica de ventana fantasma que hace que una ventana que no responde se pueda minimizar, mover y cerrar; y sobre que la deshabilitación dura durante la vida del proceso. ↩ ↩2
-
Microsoft Learn, About Messages and Message Queues. Sobre que las aplicaciones de Windows están dirigidas por eventos y el procedimiento de ventana procesa los mensajes; sobre la distinción entre mensajes encolados y mensajes enviados de forma directa; sobre el intercambio de una ventana que no responde por una ventana fantasma; y sobre la sección que cubre el interbloqueo de hilos que se envían mensajes entre sí. ↩ ↩2 ↩3
-
Microsoft Learn, Using Messages and Message Queues. Sobre una implementación típica del bucle de mensajes con GetMessage, TranslateMessage y DispatchMessage, y sobre cómo inspeccionar una cola de mensajes. ↩
-
Microsoft Learn, SendMessage function (winuser.h). Sobre que SendMessage llama al procedimiento de ventana de la ventana especificada y no regresa hasta que el procesamiento termina; sobre que un envío a una ventana de otro hilo hace esperar al emisor hasta que ese hilo procesa el mensaje; y sobre la diferencia con PostMessage, que coloca el mensaje en la cola sin esperar una respuesta. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, SendMessageTimeout function (winuser.h). Sobre poder enviar un mensaje con un tiempo de espera; y sobre una marca (SMTO_ABORTIFHUNG) que regresa sin esperar cuando la ventana no está respondiendo (ha sido juzgada colgada). ↩
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
DllMain y el bloqueo del cargador — La razón real de que le digan «no haga nada en la inicialización de la DLL»
Por qué no debe llamar a LoadLibrary ni sincronizar con otros hilos desde DllMain. A partir de fuentes primarias, este artículo explica e...
API del grupo de hilos de Win32 — Concurrencia sin crear hilos, con CreateThreadpoolWork
¿Está multiplicando las llamadas a CreateThread en el código nativo? Este artículo explica, a partir de fuentes primarias, la API del gru...
Aplicaciones que se rompen al reanudar de la suspensión — Eventos de energía y aplicaciones empresariales que sobreviven a la reanudación
Abre el portátil y las conexiones de la aplicación empresarial están cortadas: la causa es un diseño que no contempló la suspensión. Este...
Despertares espurios — por qué una variable de condición despierta «sin notificación» y cómo esperar correctamente en Windows
El wait de una variable de condición puede volver sin notificación (despertar espurio). El artículo explica, a partir de la implementació...
Cómo funcionan el portapapeles y arrastrar y soltar — tratar correctamente la transferencia de datos OLE en aplicaciones empresariales
Una tabla de Excel se deshace al pegarla y deja de pegarse al cerrar el origen: el portapapeles coloca el mismo contenido en varios forma...
Temas relacionados
Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.
Temas técnicos de Windows
Portal sobre desarrollo de Windows, investigación de fallos y aprovechamiento de activos existentes.
Hilo de UI y temporizadores
Hilo de UI de WPF / WinForms, flujos asíncronos, Dispatcher y diseño de temporizadores.
Investigación de fallos y problemas prolongados
Fallos intermitentes, diagnóstico de comunicaciones, bloqueos prolongados y pruebas de rutas de error.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Desarrollo de aplicaciones para Windows
Aplicaciones empresariales, integración de dispositivos y herramientas de comunicación, de los requisitos al desarrollo.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Bajo qué condiciones aparece «No responde»?
- El sistema operativo juzga que una ventana no responde cuando una aplicación con ventana no está esperando entrada, no está en su procesamiento de arranque y no ha recuperado un mensaje (PeekMessage) durante 5 segundos. La ventana de nivel superior así juzgada se oculta y se sustituye por una «ventana fantasma» de la misma posición, tamaño y aspecto. El texto «(No responde)» de la barra de título y el aspecto blanco esmerilado pertenecen a esta ventana fantasma, que solo permite mover, minimizar o cerrar. En otras palabras, «No responde» no es algo que muestre la propia aplicación: es una pantalla que el sistema operativo pone en su lugar.
- ¿Hay un ajuste para que «No responde» no aparezca mientras el trabajo está en curso?
- Llamar a DisableProcessWindowsGhosting deshabilita el intercambio por una ventana fantasma para ese proceso. Eso solo hace el cuelgue menos visible para el usuario: la ventana sigue sin reaccionar a la entrada y, desde el punto de vista del usuario, es una congelación completa, sin forma de moverla o cerrarla. La corrección real no es suprimir la visualización, sino mover el trabajo pesado a un hilo trabajador para que el hilo de IU no quede bloqueado ni una décima de segundo, y mucho menos cinco. Tenga en cuenta también que el sistema operativo no crea una ventana fantasma mientras hay un depurador acoplado, así que puede parecer que «No responde nunca ocurre durante la depuración».
- ¿Es aceptable evitar «No responde» con DoEvents (bombeando el bucle de mensajes a mano)?
- No se recomienda. Hacer girar DoEvents o un bucle PeekMessage en medio de un trabajo pesado esquivará el juicio de no respuesta, pero entonces cualquier controlador de eventos puede reentrar: un segundo clic del botón, cerrar la ventana, un temporizador, etc. Que otro controlador reescriba datos que aún se están procesando, o toque un formulario que ya debería estar cerrado y lance una excepción, produce errores de reentrada que dependen del momento y son difíciles de reproducir, más molestos que el propio «No responde». El enfoque adecuado es mover el propio trabajo a un hilo trabajador con Task.Run o similar, y dejar que el hilo de IU se encargue solo de mostrar el progreso y aceptar la cancelación.
- ¿Cómo actualizo la pantalla (los controles) desde un hilo trabajador?
- Los controles de WinForms y los elementos de WPF solo pueden tocarse desde el hilo que los creó (normalmente el hilo de IU). Tocarlos de forma directa desde un hilo trabajador provoca excepciones o comportamiento indefinido. En C#, async/await es el camino más fácil: la continuación después de await regresa al hilo de IU que llamó, así que puede actualizar los controles con normalidad después del await. Para cambiar de forma explícita, use Control.Invoke/BeginInvoke en WinForms y Dispatcher.InvokeAsync en WPF. En Win32 nativo, el patrón establecido es que el hilo trabajador envíe con PostMessage un mensaje de finalización personalizado al hilo de IU, y que el procedimiento de ventana actualice la pantalla.
- ¿Cómo investigo la causa de una aplicación que muestra «No responde»?
- Lo importante es capturar el estado en el momento mismo del cuelgue. Primero tome un volcado completo desde la pestaña Detalles del Administrador de tareas con «Crear archivo de volcado» y luego, en WinDbg, mire la pila del hilo de IU (el hilo que ejecuta el bucle de mensajes). Si está atascado en E/S síncrona, una espera de red, una espera de bloqueo o esperando a otro hilo a través de SendMessage, aparece en la pila tal cual. Para mirar un proceso en vivo, use la lista de hilos y la vista de pila de Process Explorer; para seguirlo a lo largo del tiempo, capture una traza con WPR. Véase también el artículo introductorio de WinDbg de este sitio, Process Explorer en la práctica y WPR/WPA en la práctica.
Perfil del autor
Página de presentación del autor del artículo.
Go Komura
Representante de KomuraSoft LLC
Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.