Suspensión, hibernación y Modern Standby: evitar con diseño que las apps de larga duración "se detengan de noche"
· Actualizado el: · Go Komura · Suspensión, Modern Standby, Gestión de energía, SetThreadExecutionState, Ejecución prolongada, Temporizadores, Aplicaciones residentes, C#, .NET, Investigación de fallos, Desarrollo en Windows, Consultoría técnica
“Se suponía que la aplicación de monitorización llevaba toda la noche funcionando, pero por la mañana el gráfico aparecía detenido a la 1:00 a. m.” “Una herramienta de recolección que llevaba meses estable en un PC de escritorio empezó a dejar huecos en los registros en cuanto se sustituyó por un portátil” — en las consultas sobre aplicaciones empresariales de ejecución prolongada, este síntoma es de los más habituales. Al revisar el registro no hay excepciones ni caídas: simplemente faltan varias horas de datos. Con mucha frecuencia, el culpable no es un error de la aplicación, sino la gestión de energía de Windows.
Lo complicado es que “suspensión” no designa un único mecanismo. La suspensión S3 tradicional, la hibernación (S4) y Modern Standby (S0 low power idle), predominante en los portátiles recientes, difieren tanto en cómo se detiene la aplicación como en la eficacia de las medidas de mitigación. Modern Standby en particular suele malinterpretarse por su reputación de “el sistema sigue funcionando incluso durante la suspensión”, pero en realidad las aplicaciones de escritorio se detienen de forma aún más activa.
En este artículo repasamos lo mínimo indispensable sobre los tipos de suspensión y el comportamiento de las aplicaciones durante ella, y organizamos cómo combinar tres enfoques de diseño —”impedir la suspensión”, “asumir la suspensión como algo dado” y “despertar el equipo a una hora determinada”— con ejemplos de implementación y una tabla de decisión.
1. Primero, las conclusiones
Como hay muchas conclusiones, las dividimos en tres bloques: las premisas (1.1), el diseño para “impedir” la suspensión (1.2) y el diseño para “seguir” la suspensión (1.3). El camino más rápido es leer 1.1, decidir si su aplicación pertenece al bloque 1.2 o al 1.3, y avanzar desde ahí al cuerpo del artículo (la tabla de decisión está en el capítulo 7).
1.1. Premisas — los tipos de suspensión y lo que ocurre mientras el equipo está detenido
- Windows tiene la suspensión S3 tradicional, la hibernación (S4) y Modern Standby (S0 low power idle), y los equipos compatibles con Modern Standby no admiten S1 a S3. Puede comprobar cuál usa su PC con
powercfg /a. 123 - Las aplicaciones de escritorio no siguen funcionando durante Modern Standby. El DAM (Desktop Activity Moderator) suspende los hilos de los procesos de escritorio (los servicios de la sesión 0 quedan en throttling). No diseñe partiendo de la premisa de que “como es S0, debería seguir funcionando”. 4
- Durante la suspensión no se ejecutan hilos, y la forma de contar el plazo de un temporizador también varía según la generación de la API. Desde Windows 8, los temporizadores y esperas con especificación relativa (
SetWaitableTimeren modo relativo,SleepEx, etc.) no avanzan su cuenta durante la suspensión y arrastran el tiempo restante hasta después de la reanudación. 56 Los temporizadores de .NET, hasta .NET 10, sí cuentan el tiempo de suspensión (si el plazo se cumplió durante la suspensión, se disparan justo al reanudar), pero está previsto que a partir de .NET 11 dejen de contarlo (.NET 11 aún no se había publicado en el momento de escribir este artículo; véase la advertencia de la sección 3.1). 6 Las API de tiempo transcurrido también se dividen entre las que incluyen el tiempo de suspensión (GetTickCount, QueryPerformanceCounter = Stopwatch) y las que no lo incluyen (QueryUnbiasedInterruptTime). 78 - Una conexión TCP que queda inactiva durante la suspensión puede ser descartada silenciosamente por el tiempo de espera de inactividad de equipos intermedios como NAT, firewalls o balanceadores de carga (por ejemplo, Azure Load Balancer descarta el flujo sin aviso a los 4 minutos por defecto). Diseñe partiendo de que, tras reanudar, será necesario reconectar. 94
1.2. Diseño para “impedir” la suspensión (capítulo 4)
- La forma estándar de inhibir la suspensión es
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED). Sin embargo, solo funciona contra la suspensión automática causada por el tiempo de espera de inactividad: no puede impedir la suspensión que el usuario indica con el botón de encendido o cerrando la tapa. Actívela solo durante el intervalo necesario y desactívela siempre al terminar. 1011 - Puede comprobar si la inhibición está funcionando con
powercfg /requests. Con las API de la familiaPowerCreateRequestpuede añadir una cadena de motivo a la solicitud, que aparece en este listado, lo que facilita la investigación operativa. 121314 - Si el requisito es directamente “que nunca entre en suspensión”, como en un PC de control de equipos que funciona 24 horas, lo correcto es deshabilitarla desde la configuración de energía, no mediante la API de inhibición de la aplicación (los ajustes concretos están en 7.1).
1.3. Diseño para “seguir” la suspensión (capítulos 5 y 6)
- Si va a asumir la suspensión como algo dado, deténgala en .NET con
SystemEvents.PowerModeChangedy en Win32 conWM_POWERBROADCAST(PBT_APMSUSPEND / PBT_APMRESUMEAUTOMATIC). El margen de la notificación de suspensión es de apenas unos 2 segundos por aplicación, y cuando la batería está muy baja el sistema puede entrar en suspensión sin ninguna notificación. 15161718 - Las aplicaciones de tipo consola o servicio, que no tienen ventana, pueden recibir la misma notificación sin un HWND mediante
PowerRegisterSuspendResumeNotification(un ejemplo mínimo se muestra en 5.1). 19 - Si necesita que un proceso se ejecute de forma fiable a una hora determinada, la primera opción es la opción “Reactivar el equipo para ejecutar esta tarea” (WakeToRun) del Programador de tareas. Mantiene el sistema despierto hasta que la tarea termina. Sin embargo, depende de que las opciones de energía tengan habilitado el permiso de temporizadores de reactivación, por lo que es imprescindible verificar en el equipo real que realmente despierta. 2021
2. La suspensión de Windows no es de un solo tipo
Antes de pensar en medidas concretas, repasemos primero los tres estados que sirven de base.
| Estado | Nombre común | Contenido | Desde el punto de vista de la aplicación |
|---|---|---|---|
| S3 | Suspensión tradicional | La CPU se detiene; solo la RAM recibe alimentación y conserva su estado | No se ejecuta ningún procesamiento1 |
| S4 | Hibernación | El contenido de la memoria se escribe en el archivo de hibernación y se corta la alimentación | Igual que S3. La reanudación restaura desde el archivo1 |
| S0 low power idle | Modern Standby | El sistema sigue funcionando parcialmente con bajo consumo y se reanuda de forma instantánea | El DAM detiene las aplicaciones de escritorio24 |
S3 y S4 son “estados en los que el sistema no ejecuta ninguna tarea de cálculo, aparentemente apagados”. 1 Modern Standby, en cambio, es un modelo cercano al de los smartphones: mantiene la conexión de red mientras la pantalla está apagada, permanece a la espera con bajo consumo y se reanuda en menos de un segundo al pulsar el botón de encendido. Los equipos compatibles con Modern Standby no usan S1 a S3. 222
Aquí es importante el DAM (Desktop Activity Moderator) presente en los equipos compatibles con Modern Standby. El DAM es el mecanismo que, al entrar en modo standby, reduce la ejecución de las aplicaciones de escritorio hasta equipararla a la suspensión S3: en los procesos de la sesión interactiva se suspenden todos los hilos, y los servicios de la sesión 0 quedan en throttling (suspendidos la mayor parte del tiempo, ejecutándose solo de forma intermitente). 4 Es decir, la expectativa de que “con Modern Standby la aplicación seguirá funcionando durante la suspensión” es justo lo contrario de lo que ocurre: desde el punto de vista de la aplicación se detiene igual que con S3, y además, a diferencia de S3, “el sistema en sí sigue funcionando mientras solo la aplicación se detiene”, lo que se manifiesta como desfases horarios o comportamientos incoherentes de los temporizadores. Conviene tenerlo presente. 4
Puede comprobar cuál de los dos modos usa su PC (o el PC del cliente) sin necesidad de permisos de administrador con el siguiente comando. 3
> powercfg /a
Los siguientes estados de suspensión están disponibles en este sistema:
Espera (S0 inactividad de bajo consumo) Conectado a la red
Hibernación
...
Si aparece Espera (S3), se trata de un equipo S3; si aparece S0 inactividad de bajo consumo, es un equipo Modern Standby. En las consultas del tipo “desde que cambié a un portátil empezaron a faltar registros”, esto es lo primero que se comprueba.
3. Qué le ocurre a la aplicación durante la suspensión y tras reanudar
3.1. Hilos y temporizadores
Durante la suspensión (S3/S4, y también durante la suspensión del DAM) no se ejecutan hilos. 14 Lo que suele pasarse por alto es cómo cuenta el tiempo de suspensión el “plazo” de un temporizador o de una espera, algo que varía según la capa de la API. Organizado con el mismo formato que la tabla de API de tiempo transcurrido de la siguiente sección, queda así:
| Tipo de temporizador o espera | Comportamiento durante la suspensión | Comportamiento tras la reanudación |
|---|---|---|
Temporizador relativo de Win32 (especificación relativa de SetWaitableTimer / SetWaitableTimerEx) |
Desde Windows 8, la cuenta no avanza (hasta Windows 7 avanzaba incluyendo el tiempo en estado de bajo consumo)5 | Se dispara tras esperar el tiempo restante5 |
API de espera con tiempo de espera (SleepEx / WaitForMultipleObjectsEx, etc.) |
Desde Windows 8, no cuenta el tiempo de inactividad6 | Arrastra el tiempo restante y continúa esperando6 |
Temporizadores administrados de .NET (System.Threading.Timer, etc.) |
Hasta .NET 10 lo cuenta (basado en GetTickCount64); está previsto que desde .NET 11 no lo cuente (basado en QueryUnbiasedInterruptTime)6 | Hasta .NET 10, si el plazo ya venció, se dispara justo al reanudar; desde .NET 11 está previsto que espere el tiempo restante antes de dispararse6 |
Temporizador con capacidad de reactivación (SetWaitableTimer con fResume en TRUE) |
Reactiva el propio sistema23 | Se reactiva y se dispara, pero si no se declara “en uso” durante el temporizador de inactividad desatendido (mínimo 2 minutos), el equipo vuelve a suspenderse (capítulo 6)23 |
La documentación del cambio descrito en la tercera fila lo presenta como un cambio disruptivo derivado de la modificación del mecanismo subyacente de Environment.TickCount64, y la propia documentación advierte de que “puede haber código que deje de dispararse justo después de la reanudación”. 6
Sobre las referencias a .NET 11
En el momento de escribir este artículo (julio de 2026), .NET 11 aún no se ha publicado. .NET sigue un ciclo anual de lanzamientos mayores en noviembre, y la versión más reciente, .NET 10, se publicó el 11 de noviembre de 2025. 24 El comportamiento de .NET 11 descrito aquí se basa en la documentación de cambios disruptivos que Microsoft ha publicado (información en fase de vista previa) y refleja lo previsto; verifique siempre la documentación más reciente una vez alcance disponibilidad general (GA). 6
Es decir, si una aplicación que mide con un System.Threading.Timer a intervalos de 10 segundos pasa 8 horas en suspensión, en ningún caso se dispararán de golpe las 8 horas acumuladas, pero si se dispara una sola vez justo al reanudar o si espera el tiempo restante antes de hacerlo depende de la capa de la API y de la versión del runtime. En cualquier caso, las muestras durante la suspensión se pierden. Es más seguro no depender de “que se dispare justo al reanudar” para reconstruir el proceso, sino reprogramar explícitamente con el evento de reanudación descrito en el capítulo 5.
3.2. La medición del tiempo transcurrido se desincroniza
Entre las API de tiempo transcurrido conviven las que incluyen el tiempo de suspensión y las que no.
| API | Tiempo de suspensión |
|---|---|
| GetTickCount / GetTickCount64 | Lo incluye7 |
| QueryPerformanceCounter (= base de Stopwatch en .NET) | Lo incluye (standby, hibernación, connected standby)8 |
| QueryUnbiasedInterruptTime | No lo incluye (solo el tiempo en working state)257 |
| Environment.TickCount / TickCount64 | Lo incluye hasta .NET 10 (basado en GetTickCount64); cambia a no incluirlo desde .NET 11 (basado en QueryUnbiasedInterruptTime)6 |
Un código del tipo “cuando Stopwatch marca 10 segundos, toma la siguiente muestra” que atraviese una suspensión terminará marcando “han pasado 8 horas y 10 segundos”, y a la inversa, la evaluación del tiempo transcurrido basada en TickCount cambiará de comportamiento con la migración a .NET 11. Si reparte las responsabilidades así — usar Stopwatch para “el tiempo que tomó el procesamiento” y DateTime/DateTimeOffset para “la hora de reloj en la que debe ejecutarse lo siguiente”, y recalcular la base en el evento de reanudación (capítulo 5) — no se verá afectado ni por la suspensión ni por las actualizaciones del runtime. El diseño de los temporizadores de ciclo corto en sí se trata en “Por qué en Windows conviene priorizar la espera por eventos sobre Sleep(1)”.
3.3. Las conexiones TCP mueren “en silencio”
Durante la suspensión, la aplicación no puede comunicarse, así que la conexión queda inactiva. El problema está en los equipos de la ruta. Los equipos intermedios como NAT, firewalls o balanceadores de carga descartan por tiempo de espera los flujos inactivos, pero en muchas configuraciones ese descarte no se notifica a ninguno de los dos extremos: simplemente se deja caer en silencio. Por ejemplo, el comportamiento predeterminado de Azure Load Balancer es “descartar silenciosamente el flujo que alcanza el tiempo de espera de inactividad (4 minutos por defecto)”. 9 Los routers internos de la empresa y las pilas TCP embebidas de los propios equipos también tienen tiempos de espera del mismo tipo.
Como resultado, tras la reanudación el socket de la aplicación parece seguir sano, pero el siguiente envío falla con un error o la aplicación se queda bloqueada esperando respuesta hasta que expira el tiempo de espera. La propia documentación del DAM indica explícitamente que hay que tener en cuenta el efecto de la suspensión de procesos sobre la vida útil de las conexiones y sobre el handshake. 4 Además, en los equipos con Modern Standby, cuando funcionan con batería, por defecto se detiene por completo la actividad de red durante la suspensión (Adaptive Connected Standby). 26 La práctica habitual es sospechar de la conexión en cuanto se recibe el evento de reanudación y descartarla para reconectar. Para diagnosticar el problema de conexiones que “parecen vivas pero están muertas”, consulte también “Causas y diagnóstico de cortes de comunicación de cámaras industriales por retransmisión TCP”.
4. Cómo “impedir” la suspensión — SetThreadExecutionState y las solicitudes de energía
Cuando el problema es que el equipo no debe entrar en suspensión solo durante las horas en que se está midiendo, la solución estándar es SetThreadExecutionState. Desde C# se invoca mediante P/Invoke.
using System.Runtime.InteropServices;
internal static class PowerGuard
{
[Flags]
private enum EXECUTION_STATE : uint
{
ES_CONTINUOUS = 0x80000000,
ES_SYSTEM_REQUIRED = 0x00000001,
ES_DISPLAY_REQUIRED = 0x00000002,
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
/// <summary>Llamar al iniciar la medición: inhibe la suspensión automática (debe llamarse desde el mismo hilo que End)</summary>
public static void Begin() =>
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS |
EXECUTION_STATE.ES_SYSTEM_REQUIRED);
/// <summary>Llamar siempre al finalizar la medición: cancela la inhibición (debe llamarse desde el mismo hilo que Begin)</summary>
public static void End() =>
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
}
Estas son las especificaciones que hay que tener presentes:
- Si se incluye
ES_CONTINUOUS, el efecto se mantiene hasta la siguiente llamada que también lo incluya. Sin él, solo se reinicia el temporizador de inactividad una vez, así que si quiere mantener el efecto debe seguir llamando periódicamente. 10 ES_SYSTEM_REQUIREDinhibe la suspensión del sistema yES_DISPLAY_REQUIREDinhibe que se apague la pantalla. Para una medición en segundo plano basta con el primero. Activar tambiénES_DISPLAY_REQUIREDcuando la pantalla podría apagarse sin problema es un desperdicio de energía. 10- No puede impedir la suspensión provocada porque el usuario pulsó el botón de encendido o cerró la tapa. Esta función solo actúa sobre la suspensión automática causada por el tiempo de espera de inactividad. La documentación oficial deja claro que debe respetarse la acción explícita del usuario. 10
- El sistema lleva la cuenta de los hilos que han llamado a
SetThreadExecutionState, y entra en suspensión cuando ese contador llega a cero y no hay entrada del usuario. 11 Si el proceso termina de forma anómala, la inhibición también desaparece, así que la preocupación de “un PC que nunca entra en suspensión porque se olvidó cancelar la inhibición” se resuelve con solo reiniciar; dicho de otro modo, esto no sirve como garantía de tolerancia a fallos. - Como indica su nombre, lo que fija esta función es el estado de ejecución del hilo que la llamó. 10 Realice la activación y la desactivación desde el mismo hilo. Como las continuaciones de
async/awaitpueden ejecutarse en un hilo distinto del grupo de subprocesos, en una implementación que llama aBegin()yEnd()con unawaitde por medio, la cancelación se ejecuta en vacío en un hilo distinto de aquel en el que se activó la inhibición, y esta permanece activa mientras el hilo original siga vivo. Es más seguro fijarlo a un hilo cuya identidad esté garantizada, como el hilo de la interfaz de usuario; si necesita un diseño que cruce hilos, use la API de solicitudes de energía basada en identificadores que se describe a continuación.
Desde Windows 7 también existe una API más reciente con el mismo propósito: las solicitudes de energía (PowerCreateRequest / PowerSetRequest / PowerClearRequest). Su ventaja práctica es que, al crear la solicitud, se puede pasar una cadena de motivo mediante REASON_CONTEXT; entre los tipos de solicitud está PowerRequestSystemRequired, y también PowerRequestExecutionRequired, que inhibe la suspensión de procesos en equipos Modern Standby. 1413 La buena práctica oficial es “activar (Set) justo antes del escenario, desactivar (Clear) inmediatamente al terminar, y liberar el identificador antes de que el proceso finalice”. 13
Sin embargo, los equipos Modern Standby tienen una restricción importante. En sistemas Modern Standby que funcionan con batería, las solicitudes SystemRequired/ExecutionRequired se cancelan 5 minutos después de superar el tiempo de espera de suspensión. Además, sea cual sea el modo de alimentación, la solicitud finaliza cuando se entra en suspensión por una acción del usuario (botón de encendido, cierre de la tapa, suspensión desde el menú Inicio). 13 Es decir, el requisito de “que un portátil siga funcionando aunque se cierre la tapa y funcione con batería” no se puede lograr solo con el esfuerzo de la aplicación. Ese tipo de requisitos debe garantizarse mediante la configuración de energía y la operativa (ajuste para no suspenderse al cerrar la tapa, alimentación por corriente alterna).
Puede comprobar si la inhibición realmente está funcionando desde un símbolo del sistema con privilegios de administrador.
> powercfg /requests
SYSTEM:
[PROCESS] \Device\HarddiskVolume3\Apps\SensorLogger.exe
powercfg /requests es un comando que enumera las solicitudes de energía que impiden la suspensión o el apagado de pantalla; sirve tanto para investigar “por qué este PC no entra en suspensión” como para verificar la inhibición de su propia aplicación. 12 Así se interpreta la salida:
- La salida se divide en encabezados según el tipo de solicitud. Los básicos son
DISPLAY(inhibición del apagado de pantalla),SYSTEM(inhibición de la suspensión) yAWAYMODE, y también aparece la categoríaEXECUTION, correspondiente aPowerRequestExecutionRequired. En el ejemplo anterior, si su aplicación aparece bajoSYSTEM:, significa que está logrando inhibir la suspensión del sistema. 1213 - El prefijo
[PROCESS]/[SERVICE]/[DRIVER]al inicio de cada línea bajo un encabezado indica el tipo de origen de la solicitud. Ese tipo y ese nombre son, tal cual, los argumentos que se pasan apowercfg /requestsoverride. 12 - En los tipos sin solicitudes correspondientes aparece una línea indicando que no hay ninguna (en un entorno en inglés,
None.). Para verificar la inhibición de su propia aplicación, basta con comprobar si su proceso aparece bajoSYSTEM. - Si añade una cadena de motivo con las API de la familia
PowerCreateRequest, esa cadena aparece en este listado, de modo que el equipo de operaciones también puede saber “para qué proceso se está inhibiendo la suspensión”. 1413
A la inversa, tenga presente también que un administrador puede configurar con powercfg /requestsoverride que se ignoren las solicitudes de un proceso concreto. 12 La premisa de diseño es que la API de inhibición es una “petición”, no algo absoluto.
Por último, una cuestión de buenas prácticas. Una implementación que mantiene activado ES_CONTINUOUS | ES_SYSTEM_REQUIRED durante toda la ejecución de la aplicación significa que una aplicación residente anula de forma permanente el plan de energía configurado por el usuario. En un portátil agota la batería, y en un PC compartido afecta también a otros usos. El principio es limitar la inhibición al “intervalo en el que realmente se está ejecutando el proceso que no debe interrumpirse por la suspensión” (el propio ejemplo de la documentación oficial es “activarla al iniciar una grabación y desactivarla al completarla” 10). Como solución temporal por parte del usuario existe también PowerToys Awake, que internamente funciona con el mismo mecanismo (un hilo que solicita el estado de ejecución). 27 Esto también sirve como referencia para decidir si implementar la inhibición en la propia aplicación o dejarla en manos de una herramienta operativa.
5. Cómo “asumir” la suspensión — detección, reconexión y registro de huecos
En muchas aplicaciones residentes de monitorización o recolección, en lugar de prohibir la suspensión, resulta un diseño más razonable convivir con ella. Lo necesario se reduce a tres puntos: “saber antes de entrar”, “saber cuándo se reanuda” y “reconstruir el estado tras la reanudación”.
En .NET, el punto de entrada es Microsoft.Win32.SystemEvents.PowerModeChanged. 15
using Microsoft.Win32;
SystemEvents.PowerModeChanged += OnPowerModeChanged;
private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
switch (e.Mode)
{
case PowerModes.Suspend:
// El margen es corto: limitarse a vaciar el búfer y registrar la hora de parada de la medición
_logger.Info("suspend at {0:O}", DateTimeOffset.Now);
_collector.Pause();
break;
case PowerModes.Resume:
// 1) Dejar constancia del intervalo con huecos como dato
_logger.Info("resume at {0:O}", DateTimeOffset.Now);
// 2) Suponer que la conexión está muerta: descartarla y reconectar
_connection.Reset();
// 3) Recalcular la programación con base en el reloj y volver a montar el temporizador
_scheduler.Rebase(DateTimeOffset.Now);
// 4) Reanudar la recolección de forma explícita solo después de terminar la reconstrucción (no dejarla en pausa)
_collector.Resume();
break;
}
}
La documentación oficial deja claras dos advertencias sobre este evento: no se dispara si no hay un bucle de mensajes en marcha (en un servicio de Windows hace falta, por ejemplo, un formulario oculto), y al ser un evento estático, si no se cancela la suscripción se produce una fuga de memoria. 15 En una aplicación con GUI se puede usar sin más, pero en una aplicación de recolección de tipo consola o servicio hay que preparar una ventana de mensajes propia que reciba WM_POWERBROADCAST de Win32, o bien usar PowerRegisterSuspendResumeNotification, que permite recibir la retrollamada sin un HWND (las notificaciones en un entorno con DAM también llegan por esta vía). 416
5.1. Recibir la reanudación en una aplicación sin ventana
En aplicaciones sin ventana, como herramientas de recolección o servicios de Windows, PowerRegisterSuspendResumeNotification requiere menos trabajo que crear una ventana de mensajes propia. Esta función se invoca especificando DEVICE_NOTIFY_CALLBACK en Flags y pasando en Recipient un puntero a una DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS que contiene la retrollamada y el contexto; si tiene éxito, devuelve ERROR_SUCCESS (0). Está disponible desde Windows 8 / Windows Server 2012. 1928
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
// Ejemplo mínimo para recibir suspensión y reanudación en una consola o servicio sin ventana
// No requiere bucle de mensajes. Windows 8 / Windows Server 2012 en adelante
internal sealed class SuspendResumeNotifier : IDisposable
{
private const uint DEVICE_NOTIFY_CALLBACK = 2; // powrprof.h
private const uint PBT_APMSUSPEND = 0x0004; // winuser.h
private const uint PBT_APMRESUMEAUTOMATIC = 0x0012; // winuser.h
private const uint ERROR_SUCCESS = 0;
// ULONG DeviceNotifyCallbackRoutine(PVOID Context, ULONG Type, PVOID Setting)
private delegate uint DeviceNotifyCallbackRoutine(IntPtr context, uint type, IntPtr setting);
[StructLayout(LayoutKind.Sequential)]
private struct DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
{
public IntPtr Callback;
public IntPtr Context;
}
[DllImport("powrprof.dll")]
private static extern uint PowerRegisterSuspendResumeNotification(
uint flags,
ref DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS recipient,
out IntPtr registrationHandle);
[DllImport("powrprof.dll")]
private static extern uint PowerUnregisterSuspendResumeNotification(IntPtr registrationHandle);
// La delegate se guarda en un campo (si quedara como variable local, el GC la recolectaría y fallaría al notificar)
private readonly DeviceNotifyCallbackRoutine _callback;
private IntPtr _registration;
// Un registro cuya cancelación falla permanece en el sistema operativo. Solo esa
// delegate hay que mantenerla viva hasta que el proceso termine (véase el Dispose más abajo)
private static readonly List<DeviceNotifyCallbackRoutine> AbandonedCallbacks = new();
public SuspendResumeNotifier()
{
_callback = OnPowerNotification;
var parameters = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
{
Callback = Marshal.GetFunctionPointerForDelegate(_callback),
Context = IntPtr.Zero,
};
uint result = PowerRegisterSuspendResumeNotification(
DEVICE_NOTIFY_CALLBACK, ref parameters, out _registration);
if (result != ERROR_SUCCESS)
{
throw new InvalidOperationException(
$"PowerRegisterSuspendResumeNotification failed with {result}.");
}
}
private uint OnPowerNotification(IntPtr context, uint type, IntPtr setting)
{
// Este método lo invoca directamente el sistema operativo (código nativo). Si una
// excepción managed sale de aquí, no hay ningún llamador managed que la reciba,
// y el proceso entero se cae. Fallos que "pueden ocurrir" en la práctica, como que
// el destino de escritura del log esté lleno o que el collector ya esté liberado,
// harían que la app muriera en cada suspensión, así que hay que resolverlo todo dentro y devolver siempre un código
try
{
switch (type)
{
case PBT_APMSUSPEND:
// Margen de unos 2 segundos. Limitarse a vaciar el búfer y registrar la hora de parada
_logger.Info("suspend at {0:O}", DateTimeOffset.Now);
_collector.Pause();
break;
case PBT_APMRESUMEAUTOMATIC:
// Llega siempre, incluso en una reanudación desatendida. Delegar la reconstrucción pesada fuera de la retrollamada
_logger.Info("resume at {0:O}", DateTimeOffset.Now);
_recovery.RequestRebuild();
break;
}
}
catch (Exception ex)
{
// También puede fallar el propio registro del log, así que hay que protegerlo aquí también
try { _logger.Error("power notification failed: {0}", ex); }
catch { /* Si aquí se relanzara, el try de arriba no tendría sentido */ }
}
return ERROR_SUCCESS; // Devuelve el código de error de Windows
}
public void Dispose()
{
if (_registration == IntPtr.Zero)
{
return;
}
uint result = PowerUnregisterSuspendResumeNotification(_registration);
if (result != ERROR_SUCCESS)
{
// Si se descarta el identificador sin haber podido cancelar el registro, el registro
// permanece vivo en el sistema operativo pero deja de poder rastrearse. Si esta instancia
// se recolecta tal cual, _callback también desaparece y el sistema operativo llama a un
// puntero de función ya liberado. En la siguiente suspensión, el proceso entero se cae, así que se mantiene viva solo la delegate
lock (AbandonedCallbacks)
{
AbandonedCallbacks.Add(_callback);
}
_logger.Error("PowerUnregisterSuspendResumeNotification failed with {0}.", result);
return; // Mientras la cancelación no se haya logrado, no se descarta el identificador
}
_registration = IntPtr.Zero;
}
}
Hay tres puntos a tener en cuenta en la implementación.
- No dejar que ninguna excepción salga de la retrollamada. Como este método lo invoca directamente el sistema operativo, si una excepción managed cruza ese límite, no hay ningún llamador managed que la reciba y todo el proceso se cae. Fallos reales como un error al escribir el log o el acceso a un objeto ya liberado harían que la aplicación muriera en cada suspensión. Envuelva todo el cuerpo en un
try/catchy devuelva siempre un código. - La retrollamada se invoca desde un hilo del sistema. El margen de unos 2 segundos para
PBT_APMSUSPENDes el mismo que en el caso de recibirlo por ventana, así que no debe hacer aquí una reconexión ni ninguna limpieza que implique la red. 1718 También es más seguro que el procesamiento de la reanudación se delegue a otro hilo mediante una bandera, en lugar de ejecutarlo directamente aquí. - No olvide cancelar el registro. Pase el identificador de registro a
PowerUnregisterSuspendResumeNotification. 29 Compruebe también el valor de retorno. Si descarta el identificador sin haber conseguido cancelarlo, el registro permanece vivo en el sistema operativo, pero se pierde la única forma de rastrearlo. Si en ese estado se recolecta la instancia, también desaparece la delegate de la retrollamada, y el sistema operativo terminará llamando a un puntero de función ya liberado. - Si prefiere recibirlo mediante un identificador de ventana, también puede pasar
DEVICE_NOTIFY_WINDOW_HANDLEaRegisterSuspendResumeNotificationde user32.dll (en ese caso llega el habitualWM_POWERBROADCAST). 30
Si va a construirla como un servicio de Windows, también puede poner CanHandlePowerEvent de ServiceBase en true y sobrescribir OnPowerEvent(PowerBroadcastStatus). Al llegar el mismo evento de energía a través del Administrador de control de servicios, puede escribirse sin P/Invoke. 31
5.2. El significado de los eventos a nivel Win32
El significado de los eventos que llegan es el mismo tanto si se reciben por ventana como por retrollamada. Conviene tenerlo claro.
- PBT_APMSUSPEND: notificación justo antes de la suspensión. Solo hay un margen de unos 2 segundos por aplicación, y si se supera, el sistema puede interrumpir el procesamiento. Limítese aquí a vaciar el búfer y registrar la hora; no escriba procesamientos que tomen tiempo, como una limpieza a través de la red. 1718
- PBT_APMRESUMEAUTOMATIC: notificación que llega siempre, en cada reanudación. Escriba aquí la reconexión y la reprogramación. 32
- PBT_APMRESUMESUSPEND: llega después de PBT_APMRESUMEAUTOMATIC cuando la reanudación se debe a una acción del usuario (o a que el usuario ha vuelto). No llega en reanudaciones automáticas como la reactivación remota, así que si escribe el “procesamiento de reanudación” únicamente aquí, la reconstrucción no se ejecutará en una reanudación desatendida. 33
- Además, en las entradas críticas en suspensión, como cuando la batería está muy baja, ni siquiera llega la notificación previa. 18 Escriba el lado de Resume de forma idempotente, para que el procesamiento de reanudación funcione correctamente incluso en los casos en los que no se recibió el evento de suspensión.
5.3. Registrar los huecos como huecos
Hay otro punto tan importante como el manejo de la reanudación: registrar los huecos como huecos. Los datos de recolección que atraviesan una suspensión no representan “ausencia de valor”, sino “no se midió porque el sistema estaba detenido”; si deja constancia de ese intervalo, con las horas de suspend/resume, tanto en el log como en los datos, evitará que quien vea después el hueco en el gráfico lo confunda con un fallo. La perspectiva de qué se debe dejar en el log de una aplicación de larga duración también se trata en “Investigación de una caída por ejecución prolongada en una cámara industrial - parte de la fuga de identificadores”.
Cabe mencionar que existe otro problema similar al de la suspensión, del tipo “de repente estaba más lento sin darse cuenta”: la limitación provocada por el modo de eficiencia (EcoQoS) de Windows 11. Consulte “Qué es el modo de eficiencia de Windows - el icono de hoja verde y cómo desactivarlo”.
6. Ejecutar de forma fiable a una hora determinada — la reactivación del Programador de tareas
Implementar un proceso basado en la hora, como “agregar datos y transferirlos a las 2:00 a. m.”, combinando un temporizador de una aplicación residente con la inhibición de la suspensión no es una buena idea, porque implica matar la suspensión toda la noche. Para este uso, la opción idónea del Programador de tareas es “Reactivar el equipo para ejecutar esta tarea” (WakeToRun).
Una tarea con WakeToRun habilitado despierta el equipo de la suspensión o la hibernación a la hora de ejecución, y mantiene el sistema despierto hasta que la tarea se completa (si ya estaba despierto, también exige mantenerlo así hasta que termine). Es posible que la pantalla siga apagada al despertar; eso es normal. 20
Sin embargo, hay condiciones para que la reactivación funcione. Como también se menciona en la guía de solución de problemas oficial, es necesario que la opción de energía “Permitir temporizadores de reactivación” (Allow Wake Timer) esté habilitada y que la configuración de reactivación de la BIOS también lo esté; no es raro que los portátiles recientes, por su diseño orientado al ahorro de energía, vengan configurados para no permitir la reactivación. 21 Con powercfg /waketimers puede enumerar los temporizadores de reactivación actualmente activos 12, así que verifique siempre en el equipo real de destino si realmente se despierta. Si quiere despertar el equipo desde su propia aplicación, también existe el recurso del temporizador con capacidad de reactivación, pasando TRUE al fResume de SetWaitableTimer. En ese caso, tras la reactivación automática, el sistema permanece despierto solo durante el temporizador de inactividad desatendido (mínimo 2 minutos), y vuelve a suspenderse pronto si la aplicación no declara que está “en uso” mediante SetThreadExecutionState. Si el procesamiento tras la reactivación es largo, la combinación correcta es sumarlo a la inhibición del capítulo 4. 23
El diseño operativo relacionado con la propia tarea —la cuenta de ejecución, el problema de terminar con el código 0x1, la prevención de ejecuciones múltiples— se resume en “Las tareas del Programador de tareas no se ejecutan o terminan con 0x1 — diagnóstico de causas y diseño operativo seguro”.
7. Tabla de decisión — ¿inhibir, responder a la reanudación o reactivar?
Los tres enfoques no son excluyentes: se combinan decidiendo cuál es el principal y cuál el secundario según el tipo de aplicación.
| Tipo de aplicación | Primera opción | Combinación y notas |
|---|---|---|
| Medición y recolección de datos (mediciones continuas de horas a días) | Inhibir con ES_SYSTEM_REQUIRED solo durante el intervalo de medición |
Implemente siempre también la respuesta a la reanudación (no puede impedir la suspensión provocada por el usuario10). En equipos Modern Standby con batería, la solicitud se corta a los 5 minutos, así que exija alimentación por corriente alterna13 |
| Procesos por lotes nocturnos y transferencias programadas | Programador de tareas + WakeToRun20 | Más eficiente y fiable que mantener la app residente con inhibición. Requiere verificar el permiso de temporizadores de reactivación21 |
| Monitorización residente y agentes de notificación | Asumir la suspensión (detectar con PowerModeChanged → reconectar, reprogramar, registrar huecos)15 | Si el equipo está dedicado principalmente a la monitorización, deshabilite la suspensión desde el plan de energía, no desde la aplicación |
| Herramientas de operación de escritorio (presentaciones, paneles en pantalla, etc.) | Combinar ES_DISPLAY_REQUIRED con ES_SYSTEM_REQUIRED solo mientras se muestra contenido10 |
Cancele siempre al terminar de mostrar. En equipos de visualización permanente, resuélvalo desde la configuración de energía |
| Control de equipos 24/7 y PC de línea de producción | Deshabilitar la suspensión desde la configuración de energía (garantizado por la operativa) | No confíe en la API de inhibición de la aplicación como red de seguridad. Use powercfg /requests en las revisiones periódicas12 |
Hay dos ejes de decisión: “¿existe una franja horaria en la que el equipo puede detenerse?” (si la hay, use el Programador de tareas; si no, use la configuración de energía) y “¿en el PC de quién se ejecuta?” (cuanto más se ejecute la aplicación en un equipo personal del usuario o en un portátil compartido, más conviene inclinarse hacia la respuesta a la reanudación en lugar de la inhibición).
7.1. En qué consiste “deshabilitar la suspensión desde la configuración de energía”
“Deshabilitar desde la configuración de energía”, que ha aparecido varias veces en la tabla, se traduce en concreto en las siguientes operaciones. Cuando, como en un PC de control de equipos, se quiere detener la suspensión mediante la operativa y no mediante la aplicación, solo queda garantizado si se llega hasta aquí.
| Qué hacer | Ubicación o comando |
|---|---|
| Poner el tiempo hasta la suspensión en “Nunca” | Configuración > Sistema > Energía (en portátiles, “Energía y batería”) > Pantalla y suspensión. O bien Panel de control > Hardware y sonido > Opciones de energía > Cambiar la configuración del plan. Por comando: powercfg /change standby-timeout-ac 0 (0 significa que no entra en suspensión)1234 |
| Revisar también el tiempo hasta apagar la pantalla | “Apagar la pantalla” en la misma ventana. Por comando: powercfg /change monitor-timeout-ac 012 |
| Cambiar el comportamiento al cerrar la tapa o pulsar el botón de encendido | Opciones de energía > Elegir el comportamiento del botón de encendido > seleccionar “No hacer nada”. Esta es la vía que SetThreadExecutionState no puede impedir10 |
| Comprobar el modo del equipo | powercfg /a. Los siguientes puntos a tener en cuenta cambian según sea un equipo S3 o Modern Standby3 |
En los equipos Modern Standby hace falta un cuidado adicional. Los equipos compatibles con Modern Standby entran en modo standby cuando se apaga la pantalla. La documentación oficial menciona como disparadores, además del botón de encendido, el cierre de la tapa y la suspensión desde el menú Inicio, el hecho de que “el sistema haya salido por inactividad”. 35 Es decir, si se piensa con la lógica de un equipo S3 y se asume que “basta con desactivar el tiempo de espera de la suspensión”, queda abierta la vía de entrar en standby a través del apagado de pantalla. Revise los tiempos de espera tanto de la suspensión como del apagado de pantalla, confirme el modo con powercfg /a, e incluya en el procedimiento la verificación, dejando el equipo real toda una noche, de que efectivamente no se detiene.
También suele olvidarse el tratamiento con batería. standby-timeout-ac es la configuración para cuando el equipo funciona con corriente alterna; con batería se usa standby-timeout-dc. Si el PC de control de equipos es un portátil, es más seguro dejar la alimentación por corriente alterna como requisito explícito (como se vio en el capítulo 4, en los equipos Modern Standby con batería, la propia solicitud de energía se corta a los 5 minutos). 13
8. Resumen
- La suspensión incluye S3, hibernación (S4) y Modern Standby (S0 low power idle), y puede distinguirlas con
powercfg /a. Incluso en equipos Modern Standby, el DAM suspende las aplicaciones de escritorio, así que no es válido asumir que “sigue funcionando durante la suspensión”. - Durante la suspensión no avanzan ni los hilos ni los temporizadores. Desde Windows 8, los temporizadores y esperas relativos no cuentan el tiempo de suspensión y arrastran el tiempo restante, y los temporizadores de .NET tendrán el mismo comportamiento a partir de .NET 11 (hasta .NET 10 pueden dispararse justo al reanudar). Las API de tiempo transcurrido se dividen entre las que incluyen el tiempo de suspensión y las que no. Use Stopwatch para el tiempo transcurrido y DateTime para el reloj de pared, y recalcule la base al reanudar.
- Las conexiones TCP mueren en silencio por el tiempo de espera de inactividad de los equipos intermedios. La práctica habitual es descartarlas y reconectar en el evento de reanudación.
- Inhiba la suspensión con
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED)solo durante el intervalo necesario. No puede impedir la suspensión provocada por el usuario, y en equipos Modern Standby con batería se corta a los 5 minutos. Verifíquelo conpowercfg /requests. - Responda a la reanudación con
SystemEvents.PowerModeChanged/WM_POWERBROADCAST. Como el margen de la notificación de suspensión es de unos 2 segundos y a veces la suspensión llega sin notificación, escriba el lado de Resume de forma idempotente y registre el intervalo con huecos. - Para la ejecución programada, la opción idónea es WakeToRun del Programador de tareas. Diseñe incluyendo tanto la configuración de energía del temporizador de reactivación como la verificación de que el equipo real efectivamente despierta.
Artículos relacionados
- Las tareas del Programador de tareas no se ejecutan o terminan con 0x1 — diagnóstico de causas y diseño operativo seguro
- Por qué en Windows conviene priorizar la espera por eventos sobre Sleep(1)
- Causas y diagnóstico de cortes de comunicación de cámaras industriales por retransmisión TCP
- Investigación de una caída por ejecución prolongada en una cámara industrial - parte de la fuga de identificadores
- Qué es el modo de eficiencia de Windows - el icono de hoja verde y cómo desactivarlo
Áreas de consultoría relacionadas
KomuraSoft LLC se dedica al diseño y la implementación de aplicaciones Windows de ejecución prolongada para medición, monitorización y recolección de datos, así como a la investigación de fallos propios de la ejecución prolongada, como “se detuvo durante la noche” o “los registros quedan con huecos”. Consúltenos también para diagnosticar síntomas difíciles de reproducir relacionados con la suspensión y la gestión de energía.
- Desarrollo de aplicaciones Windows
- Investigación de fallos y análisis de causas
- Consultoría técnica y revisión de diseño
- Contacto
Referencias
-
Microsoft Learn, System Sleeping States. Sobre que S1 a S4 son estados de suspensión que no ejecutan tareas de cálculo, que S3 solo conserva la memoria y S4 la guarda en el archivo de hibernación, y que powercfg /a permite enumerar los estados de suspensión disponibles. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, System power states. Sobre que en S0 low power idle (Modern Standby) el sistema sigue funcionando parcialmente con bajo consumo, que los sistemas SoC compatibles con Modern Standby no usan S1 a S3, y que en las transiciones críticas no se emite notificación. ↩ ↩2 ↩3
-
Microsoft Learn, Overview of Modern Standby Testing and Diagnostics. Sobre que powercfg /a permite determinar la compatibilidad con Modern Standby (mostrando Standby (S0 Low Power Idle)). ↩ ↩2 ↩3
-
Microsoft Learn, Desktop Activity Moderator. Sobre que el DAM reduce la ejecución de las aplicaciones de escritorio hasta equipararla a S3, que en los procesos de la sesión interactiva se suspenden todos los hilos y los de la sesión 0 quedan en throttling, la notificación WM_POWERBROADCAST previa a la suspensión, la incoherencia entre el comportamiento de los temporizadores o el tiempo de actividad y el reloj de pared, y la necesidad de tener en cuenta la vida útil de las conexiones. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, SetWaitableTimerEx function. Sobre que un temporizador con especificación de tiempo relativo, hasta Windows 7, incluía el tiempo en estado de bajo consumo (la cuenta atrás avanzaba incluso durante la suspensión), mientras que desde Windows 8 no lo incluye (la cuenta atrás no avanza durante la suspensión). ↩ ↩2 ↩3
-
Microsoft Learn, Environment.TickCount made consistent with Windows timeout behavior. Sobre el cambio disruptivo por el cual Environment.TickCount/TickCount64, basado en GetTickCount64 (incluye el tiempo de suspensión) hasta .NET 10, pasa a basarse en QueryUnbiasedInterruptTime (no lo incluye) desde .NET 11; sobre que, desde Windows 8, las API de espera con tiempo de espera (SleepEx/WaitForMultipleObjectsEx) habían dejado de contar el tiempo de inactividad; y sobre que este cambio puede hacer que haya código que deje de dispararse justo después de la reanudación. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, Windows Time. Sobre que el tiempo transcurrido de GetTickCount/GetTickCount64 incluye el tiempo de suspensión e hibernación, y que QueryUnbiasedInterruptTime solo incluye el tiempo en working state. ↩ ↩2 ↩3
-
Microsoft Learn, Acquiring high-resolution time stamps. Sobre que System.Diagnostics.Stopwatch en código administrado usa QPC como base de tiempo, y que QueryPerformanceCounter devuelve un número de ticks que incluye el tiempo transcurrido en standby, hibernación y connected standby. ↩ ↩2
-
Microsoft Learn, Load Balancer TCP Reset and Idle Timeout. Sobre que el comportamiento predeterminado del balanceador de carga es descartar silenciosamente el flujo al alcanzar el tiempo de espera de inactividad (4 minutos por defecto), que el envío de un reset TCP es una función que se activa explícitamente, y que como mitigación se usa TCP keep-alive. ↩ ↩2
-
Microsoft Learn, SetThreadExecutionState function. Sobre el significado de ES_CONTINUOUS/ES_SYSTEM_REQUIRED/ES_DISPLAY_REQUIRED, que sin ES_CONTINUOUS solo se reinicia el temporizador de inactividad, que no puede impedir la suspensión provocada por el usuario, y el ejemplo de uso de activarlo solo durante el procesamiento necesario y desactivarlo al terminar. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, System Sleep Criteria. Sobre que el sistema cuenta las aplicaciones o hilos que han llamado a SetThreadExecutionState y entra en suspensión cuando ese contador llega a cero y no hay entrada del usuario. ↩ ↩2
-
Microsoft Learn, Powercfg command-line options. Sobre que /requests enumera las solicitudes de energía que impiden la suspensión o el apagado de pantalla, que /requestsoverride permite ignorar las solicitudes de un proceso concreto, y que /waketimers enumera los temporizadores de reactivación activos. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, PowerSetRequest function. Sobre los tipos de solicitud como PowerRequestSystemRequired/PowerRequestExecutionRequired, que con alimentación DC en Modern Standby la solicitud se corta 5 minutos después de superar el tiempo de espera de suspensión, que la solicitud termina cuando se entra en suspensión por acción del usuario, y la buena práctica de añadir una cadena de motivo y activar (Set) justo antes y desactivar (Clear) justo después. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, PowerCreateRequest function. Sobre la creación de un objeto de solicitud de energía especificando REASON_CONTEXT, y su liberación con CloseHandle cuando ya no es necesario. ↩ ↩2 ↩3
-
Microsoft Learn, SystemEvents.PowerModeChanged Event. Sobre que es un evento que se produce en la suspensión y en la reanudación, que no se produce si no hay un bucle de mensajes en marcha (en un servicio hace falta, por ejemplo, un formulario oculto), y que al ser un evento estático se produce una fuga si no se cancela la suscripción. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, WM_POWERBROADCAST message. Sobre que los eventos de gestión de energía se notifican a la ventana como mensajes WM_POWERBROADCAST (PBT_APMSUSPEND/PBT_APMRESUMEAUTOMATIC/PBT_APMRESUMESUSPEND, etc.). ↩ ↩2
-
Microsoft Learn, PBT_APMSUSPEND event. Sobre que se notifica justo antes de la suspensión, y que el margen de procesamiento es de unos 2 segundos, tras los cuales el sistema puede interrumpirlo. ↩ ↩2 ↩3
-
Microsoft Learn, System Power Management Events. Sobre que en una suspensión de emergencia por batería crítica u otra causa similar no se realiza notificación previa, y que el procesamiento de la notificación de suspensión expira a los 2 segundos como máximo por aplicación. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, PowerRegisterSuspendResumeNotification function. Sobre especificar DEVICE_NOTIFY_CALLBACK en Flags y pasar en Recipient un puntero a DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS, que el Type de la retrollamada recibe PBT_APMSUSPEND / PBT_APMRESUMESUSPEND / PBT_APMRESUMEAUTOMATIC, que devuelve ERROR_SUCCESS si tiene éxito, y que está incluida en Powrprof.dll desde Windows 8 / Windows Server 2012. ↩ ↩2
-
Microsoft Learn, ITaskSettings::get_WakeToRun method. Sobre que WakeToRun reactiva el equipo al ejecutarse la tarea y lo mantiene despierto hasta que esta se completa, y que la pantalla puede permanecer apagada durante la reactivación. ↩ ↩2 ↩3
-
Microsoft Learn, Automatic maintenance. Sobre que, cuando la reactivación programada no funciona, se mencionan como puntos a comprobar la configuración de reactivación de la BIOS, la opción de energía “Allow Wake Timer” y la configuración WakeToRun de la tarea, y que en los portátiles recientes es habitual una configuración que no permite la reactivación desde S3. ↩ ↩2 ↩3
-
Microsoft Learn, What is Modern Standby. Sobre que Modern Standby es un modelo S0 de baja energía en reposo que logra encendido y apagado instantáneos manteniendo la conexión de red, y que la reanudación (desde el botón de encendido hasta que se enciende la pantalla) dura menos de un segundo. ↩
-
Microsoft Learn, System Wake-up Events. Sobre que poner en TRUE el fResume de SetWaitableTimer permite reactivar el sistema mediante un temporizador, que tras la reactivación automática se establece un temporizador de inactividad desatendido de al menos 2 minutos, y que el equipo vuelve a suspenderse si no se indica que está en uso mediante SetThreadExecutionState. ↩ ↩2 ↩3
-
Microsoft, .NET and .NET Core Support Policy. Sobre que los lanzamientos mayores de .NET ocurren cada noviembre, y que .NET 10 es la versión LTS publicada el 11 de noviembre de 2025. ↩
-
Microsoft Learn, QueryUnbiasedInterruptTime function. Sobre que el unbiased interrupt time solo cuenta el tiempo en working state y no incluye el tiempo de suspensión ni de hibernación. ↩
-
Microsoft Learn, Modern standby network connectivity. Sobre que, mediante Adaptive Connected Standby, cuando el equipo funciona con batería, la actividad de red durante la suspensión se detiene salvo que exista un escenario que la requiera. ↩
-
Microsoft Learn, PowerToys Awake utility. Sobre que es una utilidad que mantiene el PC despierto sin cambiar el plan de energía, que funciona generando un hilo en segundo plano que solicita el estado de la máquina, y que al cerrarse el equipo vuelve al comportamiento normal del plan de energía. ↩
-
Microsoft Learn, DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS structure. Sobre que la estructura tiene los dos miembros Callback y Context, y que la retrollamada DeviceNotifyCallbackRoutine tiene la forma ULONG(PVOID Context, ULONG Type, PVOID Setting) y devuelve un código de error de Windows. ↩
-
Microsoft Learn, PowerUnregisterSuspendResumeNotification function. Sobre pasar el identificador de registro para cancelar el registro de la notificación, y que devuelve ERROR_SUCCESS si tiene éxito. ↩
-
Microsoft Learn, RegisterSuspendResumeNotification function. Sobre que especificar DEVICE_NOTIFY_WINDOW_HANDLE en Flags hace que el evento se entregue a la ventana, y que especificar DEVICE_NOTIFY_CALLBACK permite recibirlo mediante una retrollamada. ↩
-
Microsoft Learn, ServiceBase.OnPowerEvent(PowerBroadcastStatus) Method. Sobre que es el método mediante el cual un servicio de Windows recibe los cambios de estado de energía del equipo, y que su sobrescritura presupone que la propiedad CanHandlePowerEvent esté en true. ↩
-
Microsoft Learn, PBT_APMRESUMEAUTOMATIC event. Sobre que es un evento que se entrega siempre en cada reanudación, que no indica la presencia del usuario, y que si se detecta actividad del usuario, después se entrega PBT_APMRESUMESUSPEND. ↩
-
Microsoft Learn, PBT_APMRESUMESUSPEND event. Sobre que se entrega después de PBT_APMRESUMEAUTOMATIC en una reanudación provocada o detectada por el usuario, y que en una reactivación remota solo se entrega PBT_APMRESUMEAUTOMATIC. ↩
-
Microsoft Learn, Sleep idle timeout. Sobre que es la configuración que especifica el tiempo sin actividad antes de entrar automáticamente en suspensión, y que el valor mínimo 0 significa “no entrar en suspensión”. ↩
-
Microsoft Learn, Prepare software for modern standby. Sobre que se entra en Modern Standby cuando la pantalla se apaga, que los disparadores son el botón de encendido, el cierre de la tapa, la selección de suspensión desde Configuración y la salida del sistema por inactividad, que a partir de la fase del DAM se detiene la ejecución de las aplicaciones de escritorio y los servicios de la sesión 0 quedan en throttling, y que una solicitud de energía prolonga esta fase indefinidamente con alimentación AC y como máximo 5 minutos con alimentación DC. ↩
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
MAX_PATH y las trampas de rutas y nombres de archivo en Windows — el límite de 260 caracteres, nombres reservados, el punto final y las mayúsculas y minúsculas
Analizamos las limitaciones de rutas y nombres de archivo, causa habitual de «archivo no encontrado»: el desglose de MAX_PATH=260, la act...
Trampas de las unidades de red y las rutas UNC — la gestión práctica de servidores de archivos (carpetas compartidas) en aplicaciones empresariales
Analizamos los problemas típicos al escribir en carpetas compartidas o monitorizarlas desde una app empresarial: la letra de unidad (Z:),...
Prevención de la ejecución múltiple en aplicaciones de Windows — Mutex con nombre y activación en el segundo inicio
Analizamos cómo implementar la prevención de la ejecución múltiple en apps Windows mediante un Mutex con nombre. Cubrimos Global\ y Local...
Cómo llamar a la API de Win32 desde C# de forma segura — Guía práctica de P/Invoke (DllImport / LibraryImport / CsWin32)
Repasamos los puntos prácticos de P/Invoke en C# para llamar a la API de Win32: DllImport frente a LibraryImport, generación automática c...
Buenas prácticas de multithreading en la práctica — Edición .NET: qué decidir antes de aumentar los hilos
Reglas de diseño en .NET/C# para evitar fallos y bloqueos intermitentes con hilos: usar Task en lugar de hilos propios, reducir el estado...
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.
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.
- ¿Cómo evito que el PC entre en suspensión solo mientras la aplicación se está ejecutando?
- Lo básico es llamar a SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) al iniciar el proceso y limpiarlo con SetThreadExecutionState(ES_CONTINUOUS) al finalizar. Añada ES_DISPLAY_REQUIRED solo si también necesita mantener la pantalla encendida. Esto solo funciona para la suspensión automática causada por el tiempo de espera de inactividad; no puede impedir la suspensión que el usuario indica pulsando el botón de encendido o cerrando la tapa. Puede comprobar si la inhibición está funcionando con powercfg /requests.
- ¿Qué es Modern Standby? ¿En qué se diferencia de la suspensión tradicional?
- Es un modo de suspensión más reciente, también llamado S0 low power idle, en el que el sistema sigue funcionando parcialmente con bajo consumo y puede reanudarse de forma instantánea. Los equipos compatibles con Modern Standby no admiten la suspensión S3 tradicional. Sin embargo, lo que 'sigue funcionando' es solo la actividad que el sistema operativo permite: las aplicaciones de escritorio ven sus hilos suspendidos por el DAM (Desktop Activity Moderator), por lo que desde el punto de vista de la aplicación se detienen igual que en S3. Puede comprobar qué modo usa su PC con powercfg /a.
- ¿Por qué las conexiones TCP dejan de funcionar tras reanudar desde la suspensión?
- Porque durante la suspensión la aplicación no puede comunicarse, así que la conexión queda inactiva, y los equipos intermedios de la ruta (NAT, firewalls, balanceadores de carga, etc.) descartan el flujo por tiempo de espera de inactividad. Muchos de estos equipos descartan la conexión sin avisar, de modo que el socket de la aplicación parece seguir sano: el error solo aparece en el primer envío o recepción tras la reanudación, o la aplicación se queda bloqueada hasta que expira el tiempo de espera. La práctica habitual es sospechar de la conexión en cuanto se recibe el evento de reanudación y reconstruirla.
- Para garantizar que un proceso por lotes nocturno se ejecute, ¿es mejor inhibir la suspensión o usar el Programador de tareas?
- La primera opción es la opción 'Reactivar el equipo para ejecutar esta tarea' (WakeToRun) del Programador de tareas. Despierta el PC a la hora programada y lo mantiene despierto hasta que la tarea termina, por lo que no es necesario mantener inhibida la suspensión toda la noche. Sin embargo, si el temporizador de reactivación está deshabilitado en las opciones de energía, el equipo no despertará, así que es imprescindible comprobar la configuración de 'Permitir temporizadores de reactivación' y verificar en el equipo real que realmente se despierta. Inhibir la suspensión de forma permanente desperdicia energía durante ese tiempo y constituye un diseño poco cuidadoso que sobrescribe la configuración de energía del usuario.
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.