Por qué priorizar la espera por eventos sobre Sleep(1) en Windows
· Actualizado el: · Go Komura · Desarrollo en Windows, Sincronización, Eventos, Temporizadores, Diseño
En el artículo anterior, Guía práctica de soft real-time en Windows, expliqué por qué conviene evitar los bucles periódicos que dependen de Sleep.
En este artículo me centro en un único punto de aquel tema: por qué conviene priorizar la espera por eventos (event wait) sobre una timer wait corta.
Este artículo se puede leer de forma independiente. La conclusión del artículo anterior se resume en una sola línea: “un bucle de vigilancia que deja el ritmo en manos de Sleep no garantiza ni el tiempo de espera ni el momento en que despierta, así que no debe usarse como base de un procesamiento periódico”. Con tener esto presente basta para seguir el resto sin necesidad de leer el artículo anterior.
En Windows, un diseño que usa Sleep(1) o una espera con un timeout corto para “revisar el estado cada cierto tiempo” queda inevitablemente sujeto a la granularidad del reloj del sistema y al retraso de planificación posterior.
En una configuración habitual suele partirse de una platform timer resolution del orden de 15,6 ms, así que aunque la intención sea “vuelvo a mirar dentro de 1 ms”, en la práctica la espera termina siendo bastante imprecisa.
Por otro lado, cuando lo que en realidad se quiere esperar no es el “tiempo” sino un “suceso” —la llegada de trabajo, la finalización de E/S, una solicitud de detención, un cambio de estado—, no hace falta ir a comprobarlo a intervalos regulares. Es más directo, tanto en latencia como en CPU y en consumo eléctrico, que el lado en el que ocurre el evento haga signal y el lado que espera aguarde ese event.
Las preguntas que quiero responder en este artículo son estas cuatro.
- Por qué
Sleep(1)o una timer wait corta son menos precisos de lo que parece - Por qué la event wait está menos sujeta a esa limitación
- En qué situaciones conviene elegir event en lugar de timer
- En qué casos todavía conviene usar un timer
Términos que aparecen en este artículo
Antes de empezar, dejo aquí solo las siglas que aparecen en el texto sin explicación adicional.
| Término | Significado |
|---|---|
| platform timer resolution / system clock resolution | El intervalo con que el SO actualiza la hora. La determinación del timeout de una timed wait queda atada a esta granularidad |
| ISR (Interrupt Service Routine) | El proceso que corre con la máxima prioridad cuando ocurre una interrupción. Mientras se ejecuta, el thread queda a la espera |
| DPC (Deferred Procedure Call) | Un procesamiento diferido de alta prioridad que la ISR encola para “continuar más tarde”. Para este artículo basta con leer ISR y DPC juntos como factores de retraso asociados al procesamiento de interrupciones |
| IOCP (I/O Completion Port) | El mecanismo de Windows que agrupa en una cola las notificaciones de finalización de E/S asíncrona y las entrega a un grupo de threads dedicados |
WaitOnAddress |
Una API de sincronización para “esperar hasta que cambie el valor de una dirección de memoria”. Es exclusiva del mismo proceso (se trata en el apartado 5.3) |
| hacer signal | Satisfacer la condición del lado que está esperando. En el caso de un event, es llamar a SetEvent |
1. Primero, la conclusión
- Si lo que se espera es la llegada de trabajo o la finalización de E/S, es mejor esperar un event que un timer.
- La timed wait de Windows queda inevitablemente sujeta a la granularidad del reloj del sistema.
Sleep(1)no significa “despertar exactamente al cabo de 1 ms”.- Además, aunque pase el timeout, el thread solo pasa a estar ready; la ejecución inmediata no está garantizada.
- Por eso, un diseño que en realidad está esperando un suceso pero lo hace revisando el estado con un timer sale perdiendo tanto en latencia como en consumo eléctrico.
- Conviene reservar el uso de timer solo para los casos en los que la condición es de verdad el tiempo en sí.
Dicho en términos prácticos, viene a resumirse así.
- “Enviar métricas cada 5 segundos” -> tarea de un timer
- “Actuar en cuanto entra trabajo en la cola” -> tarea de event / semaphore / condition variable /
WaitOnAddress - “Ejecutar la continuación cuando termina la E/S” -> tarea de completion / event
- “Detenerse cuando llega una solicitud de detención” -> tarea de stop event / cancellation
2. Cuál es el problema
2.1 La espera con timeout está atada a la granularidad del reloj del sistema
La precisión del timeout de las wait functions de Windows depende de la system clock resolution.
Con Sleep ocurre lo mismo: los milisegundos indicados no garantizan que la espera dure exactamente esa cantidad.
Lo importante aquí es que haber indicado 1 ms no significa que vaya a despertar exactamente 1 ms después.
Cómo comprobar la granularidad de su propio entorno
“Del orden de 15,6 ms” es una cifra general, así que lo más rápido es comprobar el valor real del propio entorno. Hay dos formas de hacerlo.
La primera es llamar a GetSystemTimeAdjustment. En el segundo argumento, lpTimeIncrement, se devuelve el intervalo con que el sistema actualiza el time-of-day clock, expresado en unidades de 100 nanosegundos. Si es del orden de 15,6 ms, el valor rondará las 150.000 unidades.
#include <windows.h>
#include <cstdio>
int main()
{
DWORD adjustment = 0;
DWORD increment = 0;
BOOL adjustmentDisabled = FALSE;
if (!GetSystemTimeAdjustment(&adjustment, &increment, &adjustmentDisabled))
{
std::printf("GetSystemTimeAdjustment failed. GetLastError=%lu\n", GetLastError());
return 1;
}
// increment está en unidades de 100 ns, así que lo convertimos a ms para verlo
std::printf("time increment = %lu (100ns) = %.4f ms\n",
increment,
increment / 10000.0);
return 0;
}
La segunda es ejecutar ClockRes, de Sysinternals. Internamente llama a esa misma GetSystemTimeAdjustment y es una herramienta pequeña que simplemente muestra la resolución del reloj del sistema, es decir, la timer resolution máxima que puede obtener una aplicación. Si se quiere comprobar sin escribir código, esta opción es más rápida.
Cabe señalar que, según la documentación, lpTimeIncrement es un valor fijo que el sistema decide al arrancar y que se describe como invariable durante la ejecución. Es decir, sirve para conocer “la granularidad nativa de este entorno”, no para medir el resultado de haber llamado a timeBeginPeriod. Ese tema se trata en el apartado 6.3.
2.2 Que se cumpla el plazo no garantiza una ejecución inmediata
Lo que complica aún más las cosas es que el thread no se ejecuta de inmediato en el instante en que se cumple el timeout.
Como también indica la documentación de Sleep, una vez terminado el tiempo de espera el thread pasa a estar ready, pero no hay garantía de que reciba CPU de inmediato y se ejecute. Influyen otros threads, la priority, el idle state de la CPU, las DPC/ISR y la contención de locks.
Es decir, una timer wait corta tiene al menos dos niveles de incertidumbre.
- La propia determinación del timeout ya está condicionada por la granularidad del timer
- Incluso después del timeout, el inicio de la ejecución queda en manos del scheduler
2.3 Sleep(1) no equivale a un ciclo de 1 ms
Al ver Sleep(1) es fácil pensar que se trata de “un loop que gira cada 1 ms”.
Pero en la práctica no debe interpretarse así.
while (!g_stop)
{
Step();
Sleep(1);
}
Lo que ocurre en realidad en este loop es esto.
- El tiempo de ejecución de
Step()se suma en cada vuelta - El propio tiempo de espera de
Sleep(1)queda atado a la granularidad - Aunque despierte, no siempre puede ejecutarse de inmediato
3. Por qué la espera por eventos es ventajosa
3.1 La condición de fin de espera pasa de ser un “tiempo agotado” a ser un “signal”
La event wait resulta ventajosa porque cambia el significado mismo de la espera.
Así funciona la timer wait.
- Aunque todavía no haya pasado nada
- Despierta cuando llega el momento fijado
- Y después de despertar comprueba “si pasó algo”
Así funciona la event wait.
- El lado en el que ocurre algo hace signal
- Cuando llega el signal, la espera queda satisfecha
- En el momento de despertar, ya existe un motivo confirmado
Al representarlo en un diagrama se ve que la propia forma en que termina la espera es distinta.
flowchart TB
accTitle: Diferencia entre timer wait y event wait
accDescr: Comparación de dos flujos de espera: el timer wait despierta por tiempo y comprueba si pasó algo, mientras que el event wait espera a que el lado que provoca el evento haga la señal antes de procesar
subgraph TimerWait["timer wait: se despierta porque llegó la hora"]
T1["Espera"] --> T2["Despierta según la granularidad del timer"]
T2 --> T3{"¿Pasó algo?"}
T3 -- "No" --> T1
T3 -- "Sí" --> T4["Procesa"]
end
subgraph EventWait["event wait: lo despiertan porque pasó algo"]
E1["Espera"] --> E2["El lado que provoca el evento hace signal"]
E2 --> E3["Al despertar, el motivo ya está confirmado"]
E3 --> E4["Procesa"]
end
Solo en el lado de la timer wait existe un bucle que vuelve en falso. Es justamente ahí donde repercute tanto en la latencia como en el consumo eléctrico.
3.2 Elegir la herramienta según qué se quiere esperar
Entonces, ¿qué herramienta elegir en la práctica? Para una primera decisión, esta tabla suele bastar.
| Qué se quiere esperar | Ejemplo poco recomendable | Primera elección |
|---|---|---|
| Que entre trabajo en la cola | Llamar a TryPop con Sleep(1) |
event / semaphore |
| Que termine la E/S | Ir a comprobar el estado con un timer | event de overlapped I/O / IOCP |
| Que llegue una solicitud de detención | Revisar el stop flag cada 100 ms | stop event / cancellation |
| Cambio de un valor dentro del mismo proceso | while (flag == 0) Sleep(1) |
WaitOnAddress |
| Que llegue una hora determinada | Forzarlo a un event | timer / waitable timer |
3.3 Un event tampoco es magia
La event wait es ventajosa en el sentido de que no necesita despertar según la granularidad del timer, pero eso no significa que se ejecute con latencia absolutamente cero en el instante en que llega el signal.
Incluso con una event wait, se siguen sufriendo estas influencias.
- Latencia del scheduler
- Priority del thread
- Power state de la CPU
- Contención de locks
- Page fault
- DPC / ISR
Aun así, como mínimo, se elimina la espera innecesaria de “quedarse dormido hasta el próximo timer tick”.
4. Antipatrones típicos
4.1 Hacer polling de una cola con Sleep(1)
Lo que más se ve es esto.
for (;;)
{
if (g_stop)
{
break;
}
WorkItem item;
if (TryPop(item))
{
Process(item);
continue;
}
Sleep(1);
}
Este planteamiento parece sencillo a primera vista, pero tiene tres problemas.
- Se despierta periódicamente aunque la cola esté vacía
- La latencia queda atada a la granularidad del timer
- También sale perdiendo en power
4.2 Vigilar el estado con Thread.Sleep(1) / Task.Delay(1)
En C# / .NET aparece el mismo olor.
while (!stoppingToken.IsCancellationRequested)
{
if (_queue.TryDequeue(out WorkItem? item))
{
await ProcessAsync(item, stoppingToken);
continue;
}
await Task.Delay(1, stoppingToken);
}
Aunque a simple vista parezca suave por usar async, en esencia el diseño sigue siendo polling.
5. Cómo corregirlo
5.1 El producer hace signal en el momento de la llegada
Si se está esperando la llegada a una cola, en lugar de polling conviene cambiar a un modelo en el que el producer hace signal.
- El producer mete un item en la cola
- Justo después de meterlo, llama a
SetEvent - El consumer espera con
WaitForSingleObjectoWaitForMultipleObjects - Al despertar, drena la cola
5.2 Esperar work y stop a la vez con WaitForMultipleObjects
Para un worker sencillo, esta forma resulta clara.
HANDLE waits[2] = { _stopEvent, _workEvent }; // index 0 = stop, index 1 = work
for (;;)
{
// Como bWaitAll = FALSE, el valor de retorno es el índice del primer handle que recibió la señal
DWORD rc = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
// Un fallo se indica con WAIT_FAILED ((DWORD)0xFFFFFFFF); el motivo solo se conoce llamando a GetLastError
if (rc == WAIT_FAILED)
{
throw std::system_error(
static_cast<int>(GetLastError()),
std::system_category(),
"WaitForMultipleObjects failed.");
}
if (rc == WAIT_OBJECT_0) // stop
{
return;
}
if (rc == WAIT_OBJECT_0 + 1) // work
{
DrainQueue();
continue;
}
// Llegar aquí es inesperado en una espera INFINITE
// (sería WAIT_TIMEOUT o algo de la familia WAIT_ABANDONED_0); no lo ignore, propague el error
throw std::runtime_error("WaitForMultipleObjects returned an unexpected value.");
}
Los puntos clave de este ejemplo son tres.
- Ha desaparecido
Sleep(1) - El producer llama a
SetEventen el momento en que llega un item - El worker espera
stopyworka la vez
Solo sobre el manejo del valor de retorno conviene añadir un matiz que es fácil pasar por alto en la práctica.
- Cuando
bWaitAllesFALSE, el valor de retorno en caso de éxito cae en el rango deWAIT_OBJECT_0aWAIT_OBJECT_0 + nCount - 1, y el resultado de restarleWAIT_OBJECT_0es el índice dentro del array. Si se escribe solo con dos ramas,== WAIT_OBJECT_0y!= WAIT_OBJECT_0 + 1, el código se rompe en cuanto se pasa a tener tres handles - Si varios reciben la señal al mismo tiempo, se devuelve el índice más pequeño. En el ejemplo anterior,
stopse coloca en el índice 0 precisamente para no dejar pasar por alto una solicitud de detención - Un fallo no se indica con una excepción, sino con el valor de retorno
WAIT_FAILED((DWORD)0xFFFFFFFF). El motivo solo se conoce llamando aGetLastError. Si se agrupa todo bajorc != valor_esperadocomo “fallo”, se pierden causas como que el handle estuviera cerrado o que faltara el permisoSYNCHRONIZE - Si se mezclan mutex entre los objetos esperados, también puede devolverse algo de la familia
WAIT_ABANDONED_0. En este ejemplo solo se espera un event, así que se trata como un caso inesperado
5.3 Dentro del mismo proceso, WaitOnAddress también es una opción
Dentro del mismo proceso, si lo único que se quiere es “esperar hasta que cambie un valor”, WaitOnAddress también es una opción bastante sólida.
Se elimina el trabajo de crear e inicializar un event y de cuidar que no se desincronice con el valor.
El criterio general para elegir entre uno y otro es, más o menos, este.
| Aspecto | event / semaphore / waitable object | WaitOnAddress |
|---|---|---|
| Alcance del objeto de espera | También sirve entre procesos. Se puede dar nombre | Solo dentro del mismo proceso |
| Quién despierta | SetEvent / ReleaseSemaphore, etc. |
WakeByAddressSingle / WakeByAddressAll |
| Preparación previa | Requiere crear el objeto del kernel y gestionar el handle | Basta con tener la variable que se espera |
| Versión disponible | Disponible desde hace mucho | Windows 8 / Windows Server 2012 o posterior |
| Enlazado | Kernel32.lib |
Synchronization.lib |
Hay tres puntos que conviene no pasar por alto al usarlo.
- Úselo siempre junto con
WakeByAddressSingleoWakeByAddressAll. Si el lado que modifica el valor no llama a esta función, el thread que espera no despierta. Use Single para despertar solo uno, o All para despertar a todos WaitOnAddresspuede volver aunque no se haya hecho signal. La propia documentación indica explícitamente que puede despertar antes de tiempo, por ejemplo en situaciones de poca memoria. Al volver, escríbalo siempre como un bucle while que relee el valor para confirmar si realmente cambió- El tamaño que se puede esperar es de 1, 2, 4 u 8 bytes
- El flag debe ser atómico.
WakeByAddressSinglesolo despierta al thread que espera; no hace atómica ni visible la escritura anterior. Si una variable normal se lee y escribe desde ambos lados, en C++ es una data race (comportamiento indefinido), y en una build optimizada la actualización puede no llegar a verse, dejando el hilo bloqueado aunque lo despierten. El lado que escribe debe usar release y el que lee, acquire, de forma coordinada
// El lado que espera y el que despierta corren en hilos distintos, así que el flag debe ser siempre atómico.
// Si un ULONG normal se lee y escribe desde ambos lados, en C++ es una data race (comportamiento indefinido):
// en una build optimizada la actualización puede quedar solo en un registro y no llegar a verse,
// de modo que el hilo, aunque lo despierten, puede seguir bloqueado
std::atomic<ULONG> g_ready{ 0 };
static_assert(std::atomic<ULONG>::is_always_lock_free,
"Se pasa a WaitOnAddress, así que debe ser lock-free");
// Forma mínima de "esperar hasta que g_ready deje de ser 0"
ULONG undesired = 0;
ULONG captured = g_ready.load(std::memory_order_acquire);
while (captured == undesired)
{
// Puede volver antes de tiempo, así que siempre hay que releer al volver
WaitOnAddress(&g_ready, &undesired, sizeof(ULONG), INFINITE);
captured = g_ready.load(std::memory_order_acquire);
}
El lado que modifica el valor lo actualiza y después despierta al que espera.
// Se escribe con release. Así, los datos preparados antes de esta línea (el payload de abajo)
// también quedan garantizados como visibles para quien los lea con acquire. Quien garantiza el orden
// es este store, no WakeByAddressSingle ── esa llamada solo despierta al hilo
// que espera; no hace atómica ni visible la escritura anterior
g_payload = ...; // Datos que se quieren pasar junto con la señal
g_ready.store(1, std::memory_order_release);
WakeByAddressSingle(&g_ready);
6. Casos en los que sí conviene usar un timer
6.1 Cuando la condición es el tiempo en sí
Por supuesto, hay situaciones en las que sí conviene usar un timer.
- Enviar métricas cada 5 segundos
- Reintentar (retry) al cabo de 200 ms
- Limpiar la caché cada minuto
- Esperar hasta una hora límite y entonces dar timeout
Aquí lo que se quiere esperar es de verdad el tiempo.
6.2 Usar un waitable timer
En Windows, si lo que se espera es “el tiempo en sí”, usar un waitable timer deja la intención más clara que apilar llamadas a Sleep sin más.
6.3 No usar timeBeginPeriod como práctica habitual
Cuando preocupa la precisión de una timer wait corta, es tentador añadir timeBeginPeriod(1).
Pero no conviene convertirlo en la primera opción de uso habitual.
Hay tres razones.
- Tiene un coste de power / performance
- En versiones recientes de Windows su comportamiento es algo más complejo
- Con frecuencia no corrige la causa raíz
7. Lista de verificación para revisiones
- ¿Se está creando un loop de vigilancia con
Sleep(1)/Thread.Sleep(1)/Task.Delay(1)? - ¿Se está haciendo timer poll cuando en realidad se espera la llegada a una cola, la finalización de E/S o una solicitud de detención?
- ¿El diseño permite que el producer o el lado de completion hagan signal?
- ¿Se puede esperar
stopyworkjuntos en una sola wait? - Si es un cambio de valor dentro del mismo proceso, ¿se puede escribir con
WaitOnAddress? - En los lugares donde se usa un timer, lo que en realidad se quiere esperar, ¿es de verdad “tiempo”?
8. Resumen
Un diseño que usa una timer wait corta en Windows para “revisar el estado cada cierto tiempo” queda inevitablemente sujeto a la granularidad del timer y al scheduler.
Por eso, Sleep(1) o un timeout corto no son una espera tan precisa como parece.
Por otro lado, cuando lo que en realidad se quiere esperar es un “suceso” —la llegada de trabajo, la finalización de E/S, una solicitud de detención, un cambio de estado—, la event wait resulta más natural.
En resumen, todo se reduce a esta línea.
Si se espera tiempo, timer; si se espera un suceso, event.
Con solo dejar clara esta distinción, el beneficio se nota de esta forma.
- La latencia se vuelve más predecible
- Se reducen los wakeups periódicos innecesarios
- También mejora en power
- La intención del código resulta más fácil de entender
9. Referencias
- Sleep function (Win32)
- Wait Functions
- WaitForSingleObject function
- WaitForMultipleObjects function
- Event Objects (Synchronization)
- Using Event Objects
- WaitOnAddress function
- WakeByAddressSingle function
- WakeByAddressAll function
- GetSystemTimeAdjustment function
- ClockRes - Sysinternals
- timeBeginPeriod function
- CreateWaitableTimerExW function
- SetWaitableTimer function
- Thread.Sleep Method (.NET)
- Results for the Idle Energy Efficiency Assessment
Artículos relacionados
Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.
Diseño de códigos en sistemas empresariales ── Cómo definir códigos de producto y cliente, y el dígito de control
Guía práctica para diseñar códigos de producto y cliente en sistemas empresariales: código significativo frente a secuencial, fórmulas de...
Suspensión, hibernación y Modern Standby: evitar con diseño que las apps de larga duración "se detengan de noche"
Analiza por qué las apps Windows de larga duración se detienen de noche, según las diferencias entre S3, hibernación y Modern Standby. Ex...
Tabla de decisión: terminar o continuar ante una excepción inesperada
Analiza si una aplicación debe terminar o continuar tras una excepción inesperada, según el daño al estado, los efectos externos, los hil...
Cómo elegir entre los 3 temporizadores de .NET - PeriodicTimer/Timer/DispatcherTimer
Diferencias entre PeriodicTimer, System.Threading.Timer y DispatcherTimer, y cómo elegirlos para el procesamiento async, los callbacks de...
Por qué usar .NET Generic Host y BackgroundService en una aplicación de escritorio
Resumimos cómo usar Generic Host y BackgroundService en herramientas y apps residentes de Windows para organizar el inicio, el procesamie...
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.
Servicios relacionados con este tema
El artículo está directamente relacionado con los siguientes servicios.
Consultoría técnica y revisión de diseño
Se trata de organizar el diseño de espera, la elección de primitivas de sincronización y el equilibrio entre latencia y consumo eléctrico en entornos soft real-time, por lo que encaja bien con una consultoría técnica y una revisión de diseño.
Desarrollo de aplicaciones para Windows
Sustituir el polling por temporizador por un diseño orientado a eventos en aplicaciones o servicios de Windows es un tema que incide directamente en la calidad de implementación del desarrollo de aplicaciones Windows.
Preguntas frecuentes
Preguntas habituales en las consultas sobre el tema del artículo.
- ¿Por qué Sleep(1) en Windows no despierta exactamente al cabo de 1 milisegundo?
- Porque la precisión del timeout en una espera con tiempo (timed wait) de Windows depende de la resolución del reloj del sistema (system clock resolution), y en una configuración habitual suele partirse de una platform timer resolution del orden de 15,6 milisegundos. Además, cuando termina el tiempo de espera, el thread solo pasa a estar ready, sin garantía de recibir CPU y ejecutarse de inmediato: influyen otros threads, la priority, el idle state de la CPU, las DPC/ISR y la contención de locks. Es decir, una timer wait corta tiene al menos dos niveles de incertidumbre: que la propia determinación del timeout está condicionada por la granularidad del timer, y que el inicio de la ejecución tras el timeout queda en manos del scheduler.
- ¿Qué problema tiene hacer polling de una cola con Sleep(1) o Task.Delay(1)?
- Hay tres problemas: se despierta periódicamente aunque la cola esté vacía, la latencia queda atada a la granularidad del timer y también sale perdiendo en consumo eléctrico. Un bucle await Task.Delay(1) en C# parece suave, pero en esencia sigue siendo polling. La forma de corregirlo es que el producer llame a SetEvent justo después de meter un item en la cola, y que el consumer espere con WaitForSingleObject o WaitForMultipleObjects. Si se espera a la vez, en una sola llamada, el evento de stop y el de work, también se reacciona de inmediato a una solicitud de detención.
- ¿Cómo se decide entre esperar con timer o con event?
- La línea divisoria es esta: si se espera tiempo, timer; si se espera un suceso, event. Un procesamiento cuya condición es el tiempo en sí, como enviar métricas cada 5 segundos, es tarea de un waitable timer. La llegada de trabajo a una cola es cosa de un event o un semaphore, la finalización de E/S encaja con el event de overlapped I/O o con un IOCP, una solicitud de detención se ajusta a un stop event o a la cancellation, y el cambio de un valor dentro del mismo proceso es lo propio de WaitOnAddress. Cuando esta línea queda clara, la latencia se vuelve más predecible, se reducen los wakeups periódicos innecesarios y la intención del código resulta más fácil de entender.
- ¿No basta con subir la precisión del timer con timeBeginPeriod(1)?
- No conviene convertirlo en la primera opción de uso habitual. Hay tres razones: tiene un coste de power y performance, en versiones recientes de Windows su comportamiento se ha vuelto algo más complejo y, con frecuencia, no corrige la causa raíz del problema. Si lo que en realidad se quiere esperar es un suceso, como la llegada de trabajo o la finalización de E/S, es más directo, en latencia, en CPU y en consumo eléctrico, cambiar el diseño hacia un modelo orientado a eventos en el que quien provoca el suceso hace signal, en lugar de subir la precisión del timer.
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.