2008-12-24 22 views
50

Tengo un método que se debe retrasar durante un período de tiempo específico.Comparar usando Thread.Sleep y Timer para la ejecución retrasada

¿Debo usar

Thread thread = new Thread(() => { 
    Thread.Sleep(millisecond); 
    action(); 
}); 
thread.IsBackground = true; 
thread.Start(); 

O

Timer timer = new Timer(o => action(), null, millisecond, -1); 

había leído algunas articles sobre el uso de Thread.Sleep es un mal diseño. Pero realmente no entiendo por qué.

Pero para usar el temporizador, el temporizador tiene un método de eliminación. Dado que la ejecución se retrasa, no sé cómo deshacerme del temporizador. ¿Tienes alguna sugerencia?

O si tiene códigos alternativos para la ejecución diferida también son apreciados.

Respuesta

39

Una diferencia es que System.Threading.Timer distribuye la devolución de llamada en un subproceso de grupo de subprocesos, en lugar de crear un nuevo subproceso cada vez. Si necesita que esto suceda más de una vez durante la vida de su aplicación, esto ahorrará la sobrecarga de crear y destruir un grupo de hilos (un proceso que consume muchos recursos, como señala el artículo que referencia), ya que lo hará solo reutilice los hilos en el grupo, y si va a tener más de un temporizador funcionando a la vez, significa que tendrá menos hilos ejecutándose al mismo tiempo (lo que también ahorrará considerables recursos).

En otras palabras, Timer va a ser mucho más eficiente. También puede ser más preciso, ya que Thread.Sleep solo garantiza que esperará al menos el tiempo que especifique (el sistema operativo puede ponerlo a dormir durante mucho más tiempo). De acuerdo, Timer aún no será exactamente exacto, pero la intención es disparar la devolución de llamada lo más cerca posible del tiempo especificado, mientras que NO es necesariamente la intención de Thread.Sleep.

En cuanto a la destrucción del temporizador, la devolución de llamada puede aceptar un parámetro, por lo que puede pasar el temporizador como parámetro y llamar a Dispose en la devolución de llamada (aunque no lo he probado, supongo que es es posible que el temporizador se bloquee durante la devolución de llamada).

Editar: No, supongo que no puede hacer esto, ya que tiene que especificar el parámetro de devolución de llamada en el propio constructor del temporizador.

Quizás algo como esto? (De nuevo, en realidad no han probado)

class TimerState 
{ 
    public Timer Timer; 
} 

... y para iniciar el temporizador:

TimerState state = new TimerState(); 

lock (state) 
{ 
    state.Timer = new Timer((callbackState) => { 
     action(); 
     lock (callbackState) { callbackState.Timer.Dispose(); } 
     }, state, millisecond, -1); 
} 

El bloqueo debe impedir la devolución de llamada del temporizador de tratar de liberar el temporizador antes de que el temporizador campo que se ha establecido.


Adición: Como el comentarista señaló, si la acción() hace algo con la interfaz de usuario, y luego usando un System.Windows.Forms.Timer es probablemente una mejor opción, ya que se ejecutará la devolución de llamada en la interfaz de usuario hilo. Sin embargo, si este no es el caso, y depende de Thread.Sleep vs. Threading.Timer, Threading.Timer es el camino a seguir.

+4

También vale la pena señalar la diferencia con System.Windows.Forms.Timer, que I * believe * llama a una función en el subproceso UI, que es realmente importante para las aplicaciones WinForms. –

+2

Para lectores futuros, 'Sleep' no garantiza que el evento sea POR LO MENOS, está documentado que podría ser menor. –

+4

Por curiosidad ... ¿dónde está eso documentado? –

13

Creo que Thread.Sleep está bien si realmente desea pausar la aplicación durante un período de tiempo específico. Creo que la razón por la que las personas dicen que es un mal diseño es porque en la mayoría de las situaciones las personas no quieren que la aplicación haga una pausa.

Por ejemplo, estaba trabajando en un cliente pop3 donde el programador estaba usando Thread.Sleep (1000) para esperar mientras el socket recuperaba el correo. En esa situación, era mejor conectar un controlador de eventos al zócalo y continuar la ejecución del programa después de que el socket se hubiera completado.

+2

Excepto en ningún ejemplo en el post es la aplicación de hecho una pausa. El póster hace una pausa en un hilo separado, sin llamar directamente a Thread.Sleep. Llamar a Thread.Sleep directamente, sí, mala idea. –

1

La única carne que tengo con el System.Timer es que la mayoría de las veces que he visto que se usa para los largos retrasos (horas, minutos) en los servicios de votación y los desarrolladores a menudo se olvide de poner en marcha el evento Antes de que empiecen el temporizador Esto significa que si inicio la aplicación o el servicio, tengo que esperar hasta que el temporizador haya transcurrido (horas, minutos) antes de que se ejecute.

Claro, esto no es un problema con el temporizador, pero creo que a menudo se usa incorrectamente porque es muy fácil de usar.

16

uso ThreadPool.RegisterWaitForSingleObject en lugar de temporizador:

//Wait 5 seconds then print out to console. 
//You can replace AutoResetEvent with a Semaphore or EventWaitHandle if you want to execute the command on those events and/or the timeout 
System.Threading.ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), (state, bTimeout) => Console.WriteLine(state), "This is my state variable", TimeSpan.FromSeconds(5), true); 
+0

Lea la respuesta aceptada. Timer ya usa el grupo de subprocesos. –

+13

Prefiero RegisterWaitForSingleObject para la lógica ... El método se ejecuta UNA VEZ cuando se acaba el tiempo ... por lo que no tiene que engañar para detener el temporizador una vez que el trabajo está hecho que no es bueno ... así que RegisterWaitForSingleObject naturalmente hace exactamente lo que quiere, timer no timer es mejor cuando quiere ejecutar una tarea varias veces a intervalos específicos .... –

+0

He añadido un ejemplo. Más detalles disponibles en esta pregunta http://stackoverflow.com/questions/1535262/net-timeouts-waitforsingleobject-vs-timer –

0

@miniscalope n no utilice ThreadPool.RegisterWaitForSingleObject en lugar de temporizador, System.Threading.Timer pondrá en cola una devolución de llamada a ejecutar en un subproceso del grupo de subprocesos cuando el ha transcurrido el tiempo y no requiere un identificador de espera, espere a que un solo objeto inmovilice un subproceso de subprocesamiento esperando a que se señalice el evento o expire el tiempo de espera antes de que el subproceso llame a la devolución de llamada.

+0

¿Tienes alguna documentación sobre esto? ¿Estás diciendo básicamente que 'ThreadPool.RegisterWaitForSingleObject' funciona como' Thread.Sleep' diciendo: * '' esperar que un objeto en particular fije un hilo de subproceso esperando a que el evento sea señalizado o el tiempo de espera expire antes de que el hilo llame a la devolución de llamada ''. *? – atconway

+0

No usa Thread.Sleep ... después de excavar a través de la fuente implica que llama al RegisterWaitForSingleObject nativo (digo implica porque el método que llama es externo pero decorado como una llamada de tiempo de ejecución interno ...) Si esa suposición es correcto, puede suponer (de [docs] (http://msdn.microsoft.com/en-us/library/windows/desktop/ms685061 (v = vs.85) .aspx)) que utilizará un espere el hilo desde el subproceso de subprocesos nativo para esperar a que se señale el objeto, luego ejecuta la devolución de llamada en un subproceso de trabajo. – Matt

2

Recuerdo implementar una solución similar a la de Eric. Esto es sin embargo un funcionamiento uno;)

class OneTimer 
    { 
     // Created by Roy Feintuch 2009 
     // Basically we wrap a timer object in order to send itself as a context in order to dispose it after the cb invocation finished. This solves the problem of timer being GCed because going out of context 
     public static void DoOneTime(ThreadStart cb, TimeSpan dueTime) 
     { 
      var td = new TimerDisposer(); 
      var timer = new Timer(myTdToKill => 
      { 
       try 
       { 
        cb(); 
       } 
       catch (Exception ex) 
       { 
        Trace.WriteLine(string.Format("[DoOneTime] Error occured while invoking delegate. {0}", ex), "[OneTimer]"); 
       } 
       finally 
       { 
        ((TimerDisposer)myTdToKill).InternalTimer.Dispose(); 
       } 
      }, 
         td, dueTime, TimeSpan.FromMilliseconds(-1)); 

      td.InternalTimer = timer; 
     } 
    } 

    class TimerDisposer 
    { 
     public Timer InternalTimer { get; set; } 
    } 
Cuestiones relacionadas