2011-01-26 23 views
6

Con referencia a esta cita de MSDN sobre la System.Timers.Timer:Sincronización de un método transcurrido Timers.Timer al parar

El evento Timer.Elapsed se eleva en un hilo ThreadPool, así que el evento -el método de gestión podría ejecutarse en un subproceso al mismo tiempo que una llamada a el método Timer.Stop se ejecuta en otro subproceso . Esto puede provocar que se genere el evento transcurrido después de llamar al método de parada . Esta condición de no se puede prevenir simplemente mediante la comparación de la propiedad SignalTime con el momento en que el método Stop se llama, porque el método de control de eventos ya esté ejecutando cuando se llama el método Stop, o podría empiece a ejecutar entre el momento cuando se llama al método Stop y el momento cuando se guarda el tiempo de parada. Si es crítico para evitar que el hilo que llama al método de parada desde la procedimiento mientras que el método de control de eventos aún se está ejecutando, utilice un robusto mecanismo más sincronización tales como la clase Monitor o el método CompareExchange. El código que usa el método CompareExchange puede ser que se encuentra en el ejemplo para el método Timer.Stop .

Puede alguien dar un ejemplo de un "mecanismo de sincronización robusta como la clase Monitor de" para explicar lo que esto significa exactamente?

Creo que significa usar un candado de alguna manera, pero no estoy seguro de cómo implementarlo.

Respuesta

11

Detención de una manera fiable System.Timers.Timer es de hecho un esfuerzo importante. El problema más grave es que los subprocesos de subprocesos que utiliza para llamar al evento transcurrido pueden realizar una copia de seguridad debido al algoritmo del planificador de subprocesos. Tener un par de llamadas respaldadas no es inusual, tener cientos es técnicamente posible.

Necesitará dos sincronizaciones, una para asegurarse de detener el temporizador solo cuando no se esté ejecutando un controlador de eventos transcurrido, otra para garantizar que estos hilos de TP respaldados no causen ningún daño. De esta manera:

System.Timers.Timer timer = new System.Timers.Timer(); 
    object locker = new object(); 
    ManualResetEvent timerDead = new ManualResetEvent(false); 

    private void Timer_Elapsed(object sender, ElapsedEventArgs e) { 
     lock (locker) { 
      if (timerDead.WaitOne(0)) return; 
      // etc... 
     } 
    } 

    private void StopTimer() { 
     lock (locker) { 
      timerDead.Set(); 
      timer.Stop(); 
     } 
    } 

Considere establecer la propiedad AutoReset en false. Eso es frágil de otra manera, el evento transcurrido se llama desde un método interno .NET que atrapa Excepción. Muy desagradable, el código del temporizador deja de funcionar sin ningún tipo de diagnóstico. No sé la historia, pero debe haber habido otro equipo en MSFT que resopló y resopló en este lío y escribió System.Threading.Timer. Muy recomendable.

+1

Guau, los cronómetros son realmente bestias insidiosas. : o – Andy

+0

Sí, esto se cae cuando estás en el avión volando a casa. Huff y puff, prueba de estrés al máximo antes de abordar. –

+0

Después de haber reflexionado un poco, parece que crea un Threading.Timer detrás de las escenas, y la devolución de llamada sí se traga excepciones. La implementación del Threading.Timer básico por sí solo parece ser mucho más limpio, así que tendré una lectura sobre eso. – Andy

0

Eso es lo que sugiere.

Monitor es la clase que utiliza el compilador C# para una instrucción lock.

Dicho esto, lo anterior es solo un problema si es un problema en su situación. Todo el enunciado básicamente se traduce en "Se puede obtener un evento del temporizador que ocurre justo después de llamar a Stop(). Si esto es un problema, tendrás que lidiar con él". Dependiendo de lo que esté haciendo su temporizador, puede ser un problema o no.

Si se trata de un problema, la página Timer.Stop muestra una forma robusta (utilizando Interlocked.CompareExchange) para manejar esto. Simplemente copie el código de la muestra y modifíquelo según sea necesario.

0

Probar:

lock(timer) { 
timer.Stop(); 
} 
+0

Esto no tendrá ningún efecto, al menos no sin un poco de otro código ... Además, sería una mala elección de los mecanismos de sincronización. –

0

Esta es una manera muy simple para evitar esta condición de carrera que se produzcan:

private object _lock = new object(); 
private Timer _timer; // init somewhere else 

public void StopTheTimer() 
{ 
    lock (_lock) 
    { 
     _timer.Stop(); 
    } 
} 

void elapsed(...) 
{ 
    lock (_lock) 
    { 
     if (_timer.Enabled) // prevent event after Stop() is called 
     { 
      // do whatever you do in the timer event 
     } 
    } 
} 
+0

Esto realmente no evitará la condición de carrera mencionada. El timerTick puede ocurrir de forma simultánea con la llamada a _timer.Stop() - y aún así ser despedido después (en ese punto, se liberará el bloqueo). –

+0

Pensé en el texto citado que el objetivo era evitar que el temporizador se detuviera si el método de manejo de eventos estaba en el medio de la ejecución. Mi código lo lograría (creo). – MusiGenesis

+0

"Esto podría provocar que el evento transcurrido se genere después de llamar al método Stop". - Eso todavía puede suceder con esta opción. - y el cuerpo del método transcurrido aún se ejecutará en su totalidad, ya que no está comprobando si el cronómetro se detuvo ... –

0

Parece que el temporizador no es seguro para subprocesos. Debe mantener todas las llamadas sincronizadas mediante bloqueo. lock (object) {} es en realidad una mano corta para una simple llamada de monitor.

Cuestiones relacionadas