2009-03-26 9 views
16

Estoy escribiendo un servicio de Windows que ejecuta una actividad de longitud variable a intervalos (una exploración y actualización de la base de datos). Necesito que esta tarea se ejecute con frecuencia, pero el código que se maneja no es seguro para ejecutar varias veces al mismo tiempo.Sincronización de un temporizador para evitar la superposición

¿Cómo puedo simplemente configurar un temporizador para ejecutar la tarea cada 30 segundos sin superponer las ejecuciones? (Supongo que System.Threading.Timer es el temporizador correcto para este trabajo, pero podría confundirse).

Respuesta

24

Puede hacerlo con un temporizador, pero debe tener algún tipo de bloqueo en el escaneo y la actualización de su base de datos. Un simple lock para sincronizar puede ser suficiente para evitar que ocurran múltiples ejecuciones.

Dicho esto, podría ser mejor iniciar un temporizador DESPUÉS de que se haya completado la operación, y simplemente usarlo una vez, luego detenerlo. Reinícielo después de su próxima operación. Esto le daría 30 segundos (o N segundos) entre eventos, sin posibilidad de superposiciones y sin bloqueo.

Ejemplo:

System.Threading.Timer timer = null; 

timer = new System.Threading.Timer((g) => 
    { 
     Console.WriteLine(1); //do whatever 

     timer.Change(5000, Timeout.Infinite); 
    }, null, 0, Timeout.Infinite); 

trabajo de inmediato ..... Finalizar ... espere 5 segundos .... trabajar inmediatamente ..... Finalizar ... espere 5 segundos ....

+3

En segundo lugar este enfoque - 'podría ser mejor iniciar un temporizador DESPUÉS de que se complete la operación ...' – hitec

+4

Tercero, este enfoque. También puede calcular el retardo del temporizador de forma dinámica para acercarse a los 30 segundos. Desactive el temporizador, obtenga la hora del sistema, actualice la base de datos, luego inicialice el siguiente temporizador para disparar 30 segundos después de la hora guardada. Con un retraso mínimo del temporizador por seguridad. – mghie

+0

cómo podemos estar seguros de que la operación se completará en 5 segundos. upvote totaliza más que @jsw, pero parece más efectivo. –

20

que haría uso de Monitor.TryEnter en el código transcurrido:

if (Monitor.TryEnter(lockobj)) 
{ 
    try 
    { 
    // we got the lock, do your work 
    } 
    finally 
    { 
    Monitor.Exit(lockobj); 
    } 
} 
else 
{ 
    // another elapsed has the lock 
} 
2

en lugar de bloqueo (que podría causar todos los escaneados programados para esperar y finalmente apilar). Puede iniciar el escaneo/actualización en un hilo y luego hacer una verificación para ver si el hilo todavía está vivo.

Thread updateDBThread = new Thread(MyUpdateMethod); 

...

private void timer_Elapsed(object sender, ElapsedEventArgs e) 
{ 
    if(!updateDBThread.IsAlive) 
     updateDBThread.Start(); 
} 
+0

Nada como disparar un evento asincrónico para iniciar un hilo. –

+0

Es cierto, pero si ejecutó el escaneo/actualización dentro del tiempo transcurrido, no podría obtener un control para verificar si estaba activo. –

11

prefiero System.Threading.Timer para este tipo de cosas, porque no tengo que pasar por el evento mecanismo de manejo:

Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, 30000); 

object updateLock = new object(); 
void UpdateCallback(object state) 
{ 
    if (Monitor.TryEnter(updateLock)) 
    { 
     try 
     { 
      // do stuff here 
     } 
     finally 
     { 
      Monitor.Exit(updateLock); 
     } 
    } 
    else 
    { 
     // previous timer tick took too long. 
     // so do nothing this time through. 
    } 
} 

Puede eliminar la necesita para el bloqueo haciendo que el temporizador sea de una sola vez y lo reinicie después de cada actualización:

// Initialize timer as a one-shot 
Timer UpdateTimer = new Timer(UpdateCallback, null, 30000, Timeout.Infinite); 

void UpdateCallback(object state) 
{ 
    // do stuff here 
    // re-enable the timer 
    UpdateTimer.Change(30000, Timeout.Infinite); 
} 
+0

Opción 2 creando múltiples hilos. – RollerCosta

1

Se podría utilizar el AutoResetEvent de la siguiente manera:

// Somewhere else in the code 
using System; 
using System.Threading; 

// In the class or whever appropriate 
static AutoResetEvent autoEvent = new AutoResetEvent(false); 

void MyWorkerThread() 
{ 
    while(1) 
    { 
    // Wait for work method to signal. 
     if(autoEvent.WaitOne(30000, false)) 
     { 
      // Signalled time to quit 
      return; 
     } 
     else 
     { 
      // grab a lock 
      // do the work 
      // Whatever... 
     } 
    } 
} 

Un poco solución "inteligente" es como sigue en pseudo-código:

using System; 
using System.Diagnostics; 
using System.Threading; 

// In the class or whever appropriate 
static AutoResetEvent autoEvent = new AutoResetEvent(false); 

void MyWorkerThread() 
{ 
    Stopwatch stopWatch = new Stopwatch(); 
    TimeSpan Second30 = new TimeSpan(0,0,30); 
    TimeSpan SecondsZero = new TimeSpan(0); 
    TimeSpan waitTime = Second30 - SecondsZero; 
    TimeSpan interval; 

    while(1) 
    { 
    // Wait for work method to signal. 
    if(autoEvent.WaitOne(waitTime, false)) 
    { 
     // Signalled time to quit 
     return; 
    } 
    else 
    { 
     stopWatch.Start(); 
     // grab a lock 
     // do the work 
     // Whatever... 
     stopwatch.stop(); 
     interval = stopwatch.Elapsed; 
     if (interval < Seconds30) 
     { 
      waitTime = Seconds30 - interval; 
     } 
     else 
     { 
      waitTime = SecondsZero; 
     } 
    } 
    } 
} 

Cualquiera de estos tiene la ventaja de que se puede apagar el hilo, simplemente señalando el evento.


Editar

debo añadir, que este código hace la suposición de que sólo tiene uno de estos MyWorkerThreads() en funcionamiento, de lo contrario se ejecutará simultáneamente.

1

He usado un mutex cuando me he querido sola ejecución: Método

private void OnMsgTimer(object sender, ElapsedEventArgs args) 
    { 
     // mutex creates a single instance in this application 
     bool wasMutexCreatedNew = false; 
     using(Mutex onlyOne = new Mutex(true, GetMutexName(), out wasMutexCreatedNew)) 
     { 
      if (wasMutexCreatedNew) 
      { 
       try 
       { 
         //<your code here> 
       } 
       finally 
       { 
        onlyOne.ReleaseMutex(); 
       } 
      } 
     } 

    } 

Siento llegar tan tarde ... Usted tendrá que proporcionar el nombre de exclusión mutua como parte de la GetMutexName() llamada.

Cuestiones relacionadas