¿Por qué Thread.Sleep es tan dañino?

A menudo lo veo mencionar que Thread.Sleep(); no debe usarse, pero no puedo entender por qué es así. Si Thread.Sleep(); puede causar problemas, ¿hay alguna solución alternativa con el mismo resultado que sería seguro?

p.ej.

 while(true) { doSomework(); i++; Thread.Sleep(5000); } 

otro es:

 while (true) { string[] images = Directory.GetFiles(@"C:\Dir", "*.png"); foreach (string image in images) { this.Invoke(() => this.Enabled = true); pictureBox1.Image = new Bitmap(image); Thread.Sleep(1000); } } 

Los problemas con llamar a Thread.Sleep se explican muy sucintamente aquí :

Thread.Sleep tiene su uso: simula operaciones largas mientras prueba / depura en un hilo MTA. En .NET no hay otra razón para usarlo.

Thread.Sleep(n) significa bloquear el hilo actual por al menos el número de timescings (o quantums del hilo) que pueden ocurrir dentro de n milisegundos. La duración de un intervalo de tiempo es diferente en diferentes versiones / tipos de Windows y diferentes procesadores, y generalmente oscila entre 15 y 30 milisegundos. Esto significa que casi se garantiza que el hilo se bloqueará durante más de n milisegundos. La probabilidad de que su hilo vuelva a despertar exactamente después de milisegundos es casi tan imposible como imposible. Entonces, Thread.Sleep tiene sentido para el tiempo .

Los hilos son un recurso limitado, toman aproximadamente 200,000 ciclos para crear y alrededor de 100,000 ciclos para destruir. Por defecto, reservan 1 megabyte de memoria virtual para su stack y usan de 2,000 a 8,000 ciclos para cada cambio de contexto. Esto hace que cualquier hilo en espera sea un gran desperdicio.

La solución preferida: WaitHandles

El error más Thread.Sleep es utilizar Thread.Sleep con un while-build ( demo y respuesta , buena entrada de blog )

EDITAR:
Me gustaría mejorar mi respuesta:

Tenemos 2 casos de uso diferentes:

  1. Estamos esperando porque sabemos un intervalo de tiempo específico en el que debemos continuar (use Thread.Sleep , System.Threading.Timer o lo que sea)

  2. Estamos esperando porque algunas condiciones cambian algún tiempo … ¡la (s) palabra (s) es / son alguna vez ! si la verificación de condición está en nuestro dominio de código, deberíamos usar WaitHandles; de lo contrario, el componente externo debería proporcionar algún tipo de gancho … ¡si no es así, su diseño es malo!

Mi respuesta cubre principalmente el caso de uso 2

ESCENARIO 1: aguarde la finalización de la tarea asíncrona: acepto que WaitHandle / Auto | ManualResetEvent se debe usar en el escenario donde un hilo está esperando que finalice la tarea en otro hilo.

ESCENARIO 2 – temporización en bucle: Sin embargo, como un mecanismo de temporización cruda (while + Thread.Sleep) está perfectamente bien para el 99% de las aplicaciones que NO requiere saber exactamente cuándo el Thread bloqueado debe “despertar *. El argumento que toma 200k ciclos para crear el subproceso tampoco son válidos: el subproceso del bucle de sincronización debe crearse de todos modos y 200k ciclos es simplemente otro número grande (dígame cuántos ciclos abre un archivo / socket / db llama?).

Entonces, si while + Thread.Sleep funciona, ¿para qué complicar las cosas? ¡Sólo los abogados de syntax serían prácticos !

Me gustaría responder esta pregunta desde una perspectiva de encoding política, que puede o no ser útil para cualquier persona. Pero particularmente cuando se trata de herramientas destinadas a progtwigdores corporativos 9-5, las personas que escriben documentación tienden a usar palabras como “no debería” y “nunca” para decir “no hagas esto a menos que realmente sepas lo que ‘doing y why’

Un par de mis otros favoritos en el mundo de C # son que te dicen “nunca llames al candado (esto)” o “nunca llamas a GC.Collect ()”. Estos dos son declarados a la fuerza en muchos blogs y documentos oficiales, y la OMI es una desinformación completa. En cierto modo, esta desinformación sirve para su propósito, ya que mantiene a los principiantes alejados de hacer cosas que no entienden antes de investigar completamente las alternativas, pero al mismo tiempo, hace que sea difícil encontrar información REAL a través de motores de búsqueda que todo parece apuntar a artículos que le dicen que no haga algo sin ofrecer respuesta a la pregunta “¿por qué no?”

Políticamente, se reduce a lo que las personas consideran “buen diseño” o “mal diseño”. La documentación oficial no debe dictar el diseño de mi aplicación. Si hay una razón técnica por la que no debe llamar a sleep (), la documentación de IMO debe indicar que está bien llamarlo en escenarios específicos, pero puede ofrecer algunas soluciones alternativas que sean independientes del escenario o más apropiadas para el otro escenarios.

Llamar claramente a “dormir ()” es útil en muchas situaciones cuando los plazos están claramente definidos en términos de tiempo real, sin embargo, existen sistemas más sofisticados para esperar y señalar hilos que deben ser considerados y entendidos antes de comenzar a dormir ( ) en su código, y arrojar sentencias sleep () innecesarias en su código generalmente se considera una táctica para principiantes.

Es el 1) .spinning y 2) .polling loop de tus ejemplos que las personas advierten contra , no la parte Thread.Sleep (). Creo que Thread.Sleep () generalmente se agrega para mejorar fácilmente el código que está girando o en un ciclo de sondeo, por lo que solo está asociado con el código “malo”.

Además, la gente hace cosas como:

 while(inWait)Thread.Sleep(5000); 

donde no se accede a la variable en Espera de una manera segura para subprocesos, lo que también causa problemas.

Lo que los progtwigdores quieren ver son los hilos controlados por las construcciones de Eventos y Señalización y Bloqueo, y cuando lo haga no tendrá necesidad de Thread.Sleep (), y las preocupaciones sobre el acceso variable seguro para subprocesos también se eliminan. Como ejemplo, ¿podría crear un controlador de eventos asociado con la clase FileSystemWatcher y usar un evento para activar su segundo ejemplo en lugar de un bucle?

Como mencionó Andreas N., leer Threading in C #, de Joe Albahari , es realmente muy bueno.

El modo de suspensión se utiliza en casos donde progtwigs independientes sobre los cuales usted no tiene control pueden usar a veces un recurso comúnmente utilizado (por ejemplo, un archivo), que su progtwig necesita acceder cuando se ejecuta, y cuando el recurso está siendo utilizado por estos otros progtwigs, su progtwig está bloqueado para usarlo. En este caso, cuando accedes al recurso en tu código, pones tu acceso al recurso en un try-catch (para atrapar la excepción cuando no puedes acceder al recurso), y pones esto en un ciclo while. Si el recurso es gratis, nunca se llama al sueño. Pero si el recurso está bloqueado, entonces duerme durante un período de tiempo apropiado e intenta acceder nuevamente al recurso (esta es la razón por la que está bucleando). Sin embargo, tenga en cuenta que debe poner algún tipo de limitador en el bucle, por lo que no es un bucle potencialmente infinito. Puede establecer su condición límite en N bashs (esto es lo que suelo usar), o consultar el reloj del sistema, agregar una cantidad fija de tiempo para obtener un límite de tiempo y dejar de intentar el acceso si alcanza el límite de tiempo.

Encontré una forma de hackear Thread.Sleep haciendo que el progtwig se active periódicamente y haga eventos. Vea abajo:

 // Pay attention every 1/10 sec to maintain some UI responsiveness while (val > 0) { Thread.Sleep(100); val = val - 100; Application.DoEvents(); if (val == x) { // You could do some conditional stuff here at specific times } } 

Sustituya val con el retraso deseado en milisegundos. Cuanto más Thread.Sleep el valor ms de Thread.Sleep más receptivo parecerá el progtwig. 100ms me parece aceptable. 50 ms es lo mejor

Estoy de acuerdo con muchos aquí, pero también creo que depende.

Recientemente hice este código:

 private void animate(FlowLayoutPanel element, int start, int end) { bool asc = end > start; element.Show(); while (start != end) { start += asc ? 1 : -1; element.Height = start; Thread.Sleep(1); } if (!asc) { element.Hide(); } element.Focus(); } 

Era una función animada simple, y usé Thread.Sleep en ella.

Mi conclusión, si hace el trabajo, úsala.

Para aquellos de ustedes que no han visto un argumento válido contra el uso de Thread.Sleep en SCENARIO 2, realmente hay uno: la salida de la aplicación se mantiene en el ciclo while (SCENARIO 1/3 es simplemente estúpido, por lo que no merece más mencionando)

Muchos que pretenden estar en el saber, gritando Thread.Sleep is evil no mencionaron una sola razón válida para aquellos de nosotros que exigimos una razón práctica para no usarla, pero aquí está, gracias a Pete – Thread.Sleep es Evil (se puede evitar fácilmente con un temporizador / controlador)

  static void Main(string[] args) { Thread t = new Thread(new ThreadStart(ThreadFunc)); t.Start(); Console.WriteLine("Hit any key to exit."); Console.ReadLine(); Console.WriteLine("App exiting"); return; } static void ThreadFunc() { int i=0; try { while (true) { Console.WriteLine(Thread.CurrentThread.ThreadState.ToString() + " " + i); Thread.Sleep(1000 * 10); i++; } } finally { Console.WriteLine("Exiting while loop"); } return; }