2011-03-24 51 views
30

Cada N minutos que queremos ejecutar a través de una lista de tareas. Así que hemos creado un ejecutor de tareas con unAlternativas a Thread.Sleep()

do { DoWork(); }while(!stopRequested) 

Ahora queremos tener una pausa entre los ciclos de trabajo. Todo el mundo parece pensar que Thread.Sleep() es el demonio. He visto mencionar el uso de Monitor/Evento, pero no tenemos a alguien más diciéndonos que hagamos el trabajo. Solo queremos hacer cosas cada N minutos como un reloj.

Entonces, ¿hay una alternativa o he encontrado un uso válido de Thread.Sleep?

Alguien en otra publicación mencionó WaitHandle.WaitOne() como alternativa pero aparentemente no se puede llamar a eso desde un método no estático? O al menos no puedo porque me sale un error de tiempo de compilación de ..

Se requiere una referencia de objeto para el campo no estático, método o propiedad 'System.Threading.WaitHandle.WaitOne (System.TimeSpan) '

Respuesta

22

Tiene que llamar WaitOneen a WaitHandle, sin duda. Es un método de instancia. De lo contrario, ¿cómo sabría qué esperar?

Definitivamente es mejor tener algo puede reaccionar en lugar de dormir, por lo que puede notar la cancelación sin esperar minutos sin motivo. Otra alternativa a WaitHandle es usar Monitor.Wait/Pulse.

Sin embargo, si está utilizando .NET 4 me gustaría ver lo que ofrece la Biblioteca paralela de tareas ... está en un nivel ligeramente más alto que las otras opciones, y en general es una biblioteca bien pensada.

Para las tareas regulares de trabajo es posible que desee buscar en el uso de un Timer (ya sea System.Threading.Timer o System.Timers.Timer) o posiblemente incluso Quartz.NET.

+0

¿Crees que la respuesta sigue siendo válida? – Cer

+0

@Cer: Aún debería funcionar, claro. Una alternativa es usar una tarea con un token de cancelación. –

7

Thread.Sleep no es el demonio, puede usarlo para un escenario como este. No es muy confiable por cortas duraciones.

Usar WaitHandle es una buena opción, pero necesita una instancia específica de un identificador de espera. No hará esto solo, sin embargo.

Dicho esto, la mayor parte del tiempo, las operaciones de este tipo son más adecuados para el uso de un temporizador. ¿Hay alguna razón por la que intente procesar esto en un bucle en lugar de simplemente usar un temporizador para comenzar el elemento de trabajo?

+1

Uhhh vamos a ir con la falta de imaginación en cuanto a la razón por la que no estoy usando un temporizador :) –

+0

No, 'Thread.Sleep' no es el diablo. Se parece más a Phil, el Príncipe de la Luz Insuficiente (http://en.wikipedia.org/wiki/List_of_Dilbert_characters#Phil_the_Prince_of_Insufficient_Light). En serio, sin embargo, hay relativamente pocos usos buenos de 'Thread.Sleep'. Para mí, es una bandera roja en el código. La mayoría de las veces, reemplazando un 'Sleep' con un temporizador da como resultado un código más limpio y robusto. –

5

Las tres opciones que se me ocurre de la parte superior de la cabeza son:

pero estoy seguro de que muchos mencionarán: Thread.Sleep() no es tan malo.

2

Puede usar un ManualResetEvent que establezca cuando sea el momento de detenerse, y luego hacer un WaitOne en él.

2

Usaría un temporizador de espera que señala un AutoResetEvent. Su hilo debe esperar a este objeto WaitHandle. Aquí es una pequeña aplicación de consola que muestra este enfoque:

class Program { 
    const int TimerPeriod = 5; 
    static System.Threading.Timer timer; 
    static AutoResetEvent ev; 
    static void Main(string[] args) 
    { 
     ThreadStart start = new ThreadStart(SomeThreadMethod); 
     Thread thr = new Thread(start); 
     thr.Name = "background"; 
     thr.IsBackground = true; 
     ev = new AutoResetEvent(false); 
     timer = new System.Threading.Timer(
      Timer_TimerCallback, ev, TimeSpan.FromSeconds(TimerPeriod), TimeSpan.Zero); 
     thr.Start(); 
     Console.WriteLine(string.Format("Timer started at {0}", DateTime.Now)); 
     Console.ReadLine(); 
    } 

    static void Timer_TimerCallback(object state) { 
     AutoResetEvent ev = state as AutoResetEvent; 
     Console.WriteLine(string.Format 
      ("Timer's callback method is executed at {0}, Thread: ", 
      new object[] { DateTime.Now, Thread.CurrentThread.Name})); 
     ev.Set(); 
    } 

    static void SomeThreadMethod() { 
     WaitHandle.WaitAll(new WaitHandle[] { ev }); 
     Console.WriteLine(string.Format("Thread is running at {0}", DateTime.Now)); 
    } 
} 
0

Se puede usar un System.Timers.Timer y ejecutar la obra en su controlador transcurrido.

0

Como han dicho otros respondedores, los temporizadores pueden funcionar bien para usted.

Si quieres tu propio hilo, no usaría Thread.Sleep aquí, aunque solo sea porque si necesitas cerrar la aplicación, no hay una buena manera de decirle que salga de la suspensión. He usado algo así antes.

class IntervalWorker 
{ 
    Thread workerThread; 
    ManualResetEventSlim exitHandle = new ManualResetEventSlim(); 

    public IntervalWorker() 
    { 
     this.workerThread = new Thread(this.WorkerThread); 
     this.workerThread.Priority = ThreadPriority.Lowest; 
     this.workerThread.IsBackground = true; 
    } 

    public void Start() 
    { 
     this.workerThread.Start(); 
    } 

    public void Stop() 
    { 
     this.exitHandle.Set(); 
     this.workerThread.Join(); 
    } 

    private void WorkerThread() 
    { 
     int waitTimeMillis = 10000; // first work 10 seconds after startup. 

     while (!exitHandle.Wait(waitTimeMillis)) 
     { 
      DoWork(); 

      waitTimeMillis = 300000; // subsequent work at five minute intervals. 
     } 
    } 
} 
0

Yo he usado tanto un temporizador en una aplicación Windows Forms y una aplicación de consola empecé periódicamente por un ScheduledTask en el servidor. El WinForms con un temporizador se ha utilizado en los casos en que quiero que aparezca notificaciones que se ejecutó y lo que encontró (he configurado estos para mimetizar a la bandeja del sistema). Para las situaciones en las que solo quiero recibir notificaciones si algo falla en un servidor, las coloco en el servidor como aplicaciones de consola y las ejecuto como tareas programadas. Eso parece funcionar bastante bien.

Creo que traté de usar Sleep en las aplicaciones donde ahora uso Timers, y no me gustó el resultado. Para empezar, con un temporizador puedo llamar la aplicación minimizada muy fácilmente para establecer o cambiar las configuraciones en el frente. Si la aplicación está dormida, tiene dificultades para recuperar el acceso mientras está durmiendo.

1

Si todo el hilo está haciendo es algo así como:

while (!stop_working) 
{ 
    DoWork(); 
    Thread.Sleep(FiveMinutes); 
} 

entonces sugeriría que no utilizan un hilo en absoluto. En primer lugar, no hay una razón particularmente buena para incurrir en la sobrecarga del sistema de un hilo dedicado que pasa la mayor parte del tiempo durmiendo. En segundo lugar, si configura el indicador stop_working 30 segundos después de que el hilo deja de dormir, tendrá que esperar cuatro minutos y medio antes de que el hilo se active y finalice.

me gustaría sugerir que otros han: utilizar un temporizador:

System.Threading.Timer WorkTimer; 

// Somewhere in your initialization: 

WorkTimer = new System.Threading.Timer((state) => 
    { 
     DoWork(); 
    }, null, TimeSpan.FromMinutes(5.0), TimeSpan.FromMinutes(5.0)); 

Y para cerrar:

WorkTimer.Dispose(); 
47

Por mi entendimiento, Thread.Sleep() es malo, ya que obliga al los recursos del subproceso están fuera del caché, por lo que deben cargarse nuevamente después. No es gran cosa, pero podría agravar los problemas de rendimiento en situaciones de mucha carga. Y luego está el hecho de que el tiempo no es precisa, y que efectivamente no puede esperar a que las duraciones por debajo de aproximadamente 10 ms ...

Puedo usar este fragmento:

new System.Threading.ManualResetEvent(false).WaitOne(1000); 

fácil como pastel y todo encaja en una línea. Crea un nuevo controlador de eventos que nunca se establecerá, y luego espera el período de tiempo de espera completo, que se especifica como el argumento WaitOne().

Aunque, para este escenario específico, un temporizador, probablemente sería un enfoque más apropiado:

var workTimer = new System.Threading.Timer(
    (x) => DoWork(), 
    null, 
    1000, // initial wait period 
    300000); // subsequent wait period 

Entonces, en lugar de establecer una variable de una cancelación, se detendría el temporizador con workTimer.Stop().


Editar:

Dado que las personas siguen encontrando esto útil, debo añadir que .NET 4.5 introduce el método Task.Delay, lo que es aún más conciso y también soporta asíncrono:

Task.Delay(2000).Wait(); // Wait 2 seconds with blocking 
await Task.Delay(2000); // Wait 2 seconds without blocking 
+2

Su fragmento de 1 línea me pareció genial cuando busqué una alternativa para thread.sleep. Solo quería hacer una pausa por un segundo o 2 y poder iniciar sesión en la consola cada vez que no funcionaba con thread.sleep. – Sam

+2

Noto que 'ManualResetEvent' es' IDisposable', pero su código permite que la instancia anónima obtenga GC'd en su lugar. ¿Hay algún riesgo de no llamar explícitamente a 'Dispose' en este caso? – Dai

+1

Buena pregunta. Por lo que puedo ver en el desmontaje, 'ManualResetEvent' utiliza la funcionalidad básica' WaitHandle', cuyo método Dispose simplemente descarta su instancia interna 'SafeWaitHandle'. 'SafeHaitHandle' de la clase base' SafeHandle' llama a su propio método Dispose explícitamente en su deconstructor de todos modos, así que supongo que cualquier OS OS no administrado se limpiaría en ese punto cuando el objeto es GC'd. Pero la mejor apuesta en 2016 es usar 'Task.Delay' y evitar todo el problema. :) –

-1

Encontré una forma de hackear Thread.Sleep haciendo que el programa se active periódicamente y realice eventos si necesita cancelar. Vea a continuación:

// 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 
    } 
} 

Sustituto val con su retardo deseado en milisegundos. Cuanto más baje el valor ms de Thread.Sleep, más receptivo será el programa. 100ms me parece aceptable. 50 ms es lo mejor