2009-03-26 11 views
16

Sto scrivendo un servizio di Windows che esegue un'attività di lunghezza variabile a intervalli (scansione e aggiornamento del database). Ho bisogno che questa attività venga eseguita frequentemente, ma il codice da gestire non è sicuro da eseguire più volte contemporaneamente.Sincronizzazione di un timer per evitare sovrapposizioni

Come posso semplicemente impostare un timer per eseguire l'operazione ogni 30 secondi senza mai sovrapporre le esecuzioni? (Sto assumendo che System.Threading.Timer sia il timer corretto per questo lavoro, ma potrebbe essere errato).

risposta

24

Si potrebbe fare con un timer, ma è necessario avere qualche forma di blocco sulla scansione del database e l'aggiornamento. Un semplice lock da sincronizzare potrebbe essere sufficiente per impedire il verificarsi di più esecuzioni.

Detto questo, potrebbe essere meglio avviare un timer DOPO che l'operazione è stata completata, e basta usarla una volta, quindi interromperla. Riavvia dopo la tua prossima operazione. Questo ti darebbe 30 secondi (o N secondi) tra gli eventi, senza possibilità di sovrapposizioni e nessun blocco.

Esempio:

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); 

lavoro immediatamente ..... Fine ... attendere 5 secondi .... lavorare immediatamente ..... Fine ... attendere 5 sec ....

+3

In secondo luogo questo approccio - 'potrebbe essere meglio avviare un timer DOPO che l'operazione è completa ...' – hitec

+4

Io terzo questo approccio. È anche possibile calcolare il ritardo del timer in modo dinamico per avvicinarsi ai 30 secondi. Disattiva il timer, recupera l'ora del sistema, aggiorna il database, quindi inizializza il timer successivo per sparare 30 secondi dopo l'ora salvata. Con un ritardo minimo del timer per la sicurezza. – mghie

+0

come possiamo essere sicuri che l'operazione sarà completata in 5 secondi. l'upvote totalizza più di @jsw, ma sembra più efficace. –

20

userei Monitor.TryEnter nel codice trascorso:

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

al posto di blocco (che potrebbe causare tutte le scansioni a tempo di aspettare e alla fine impilare fino). È possibile avviare la scansione/aggiornamento in un thread e quindi fare semplicemente un controllo per vedere se il thread è ancora vivo.

Thread updateDBThread = new Thread(MyUpdateMethod); 

...

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

Non è come eseguire un evento asincrono per avviare un thread. –

+0

Vero, ma se si eseguiva la scansione/aggiornamento entro il tempo trascorso, non si poteva ottenere un handle su di esso per verificare se fosse vivo. –

11

preferisco System.Threading.Timer per cose come questa, perché non c'è bisogno di passare attraverso l'evento meccanismo di gestione:

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

È possibile eliminare il necessario per il blocco rendendo il timer un one-shot e riavviarlo dopo ogni aggiornamento:

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

Opzione 2 creazione di più thread. – RollerCosta

1

È possibile utilizzare l'AutoResetEvent come segue:

// 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 po soluzione "intelligente" è come seguire in pseudo-codice:

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

Uno di questi ha il vantaggio che si può spegnere il thread, solo segnalando l'evento.


Modifica

devo aggiungere, che questo codice si basa sull'ipotesi che hai solo uno di questi MyWorkerThreads() in esecuzione, altrimenti sarebbero eseguiti contemporaneamente.

1

ho usato un mutex quando ho voluto singola esecuzione: metodo

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(); 
       } 
      } 
     } 

    } 

Scusate il ritardo ... così Sarà necessario fornire il nome mutex come parte del GetMutexName() chiamata.

Problemi correlati