2009-03-26 7 views
16

Ich schreibe einen Windows-Dienst, der eine Aktivität mit variabler Länge in Intervallen ausführt (Datenbankscan und Aktualisierung). Ich brauche diese Aufgabe häufig auszuführen, aber der zu behandelnde Code ist nicht sicher, mehrere Male gleichzeitig ausgeführt zu werden.Synchronisieren eines Timers, um Überlappungen zu vermeiden

Wie kann ich am einfachsten einen Timer einrichten, um die Aufgabe alle 30 Sekunden auszuführen, ohne dass die Ausführung wiederholt wird? (Ich nehme an, System.Threading.Timer ist der richtige Timer für diesen Job, könnte aber falsch sein).

Antwort

24

Sie könnten es mit einem Timer tun, aber Sie müssten eine Art von Sperren für Ihre Datenbank-Scan und Update haben. Eine einfache lock Synchronisierung kann ausreichen, um das Auftreten mehrerer Runs zu verhindern.

Das gesagt, es könnte besser sein, einen Timer zu starten, nachdem Sie den Vorgang abgeschlossen haben, und verwenden Sie es nur einmal, dann stoppen Sie es. Starten Sie es nach Ihrer nächsten Operation neu. Dies würde Ihnen 30 Sekunden (oder N Sekunden) zwischen den Ereignissen geben, ohne Überschneidungen und ohne Sperren.

Beispiel:

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

Arbeit sofort ..... Fertig ... 5 Sekunden warten .... sofort arbeiten ..... Fertig ... warten 5 Sekunden ....

+3

Ich zweitens diesen Ansatz - "es könnte besser sein, einen Timer zu starten, nachdem Sie den Vorgang abgeschlossen sind ..." – hitec

+4

Ich drittens diesen Ansatz. Sie können die Zeitverzögerung auch dynamisch berechnen, um näher an die 30 Sekunden zu kommen. Deaktivieren Sie den Timer, holen Sie die Systemzeit, aktualisieren Sie die Datenbank und initialisieren Sie den nächsten Timer 30 Sekunden nach der gespeicherten Zeit. Mit einer minimalen Zeitverzögerung für die Sicherheit. – mghie

+0

wie wir sicher sein können, dass der Vorgang in 5 Sekunden abgeschlossen ist. upvote summiert sich mehr als @jsw, aber es sieht effektiver aus. –

20

I Monitor.TryEnter in Ihrem verstrichene Code verwenden würde:

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

statt Sperren (die alle Ihre timed Scans dazu führen könnten, zu warten und schließlich stapeln). Sie könnten den Scan/Update in einem Thread starten und dann einfach überprüfen, ob der Thread noch am Leben ist.

Thread updateDBThread = new Thread(MyUpdateMethod); 

...

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

Nichts wie das Auslösen eines asynchronen Ereignisses zum Starten eines Threads. –

+0

Richtig, aber wenn Sie den Scan/Update innerhalb der verstrichenen Zeit ausgeführt haben, konnten Sie ihn nicht kontrollieren, um zu sehen, ob er noch aktiv war. –

11

Ich ziehe System.Threading.Timer für Dinge wie diese, weil ich zu der Veranstaltung nicht durch Mechanismus Handhabung:

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

Sie die beseitigen Notwendigkeit für das Schloss, indem der Timer einmalig gemacht und nach jedem Update neu gestartet wird:

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

Option 2 zum Erstellen mehrerer Threads. – RollerCosta

1

könnten Sie die Autoreset wie folgt verwenden:

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

Ein etwas „intelligente“ Lösung ist, wie in Pseudo-Code folgen:

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

davon Entweder hat den Vorteil, dass Sie Herunterfahren können der Thread, nur durch die Signalisierung des Ereignisses.


bearbeiten

Ich sollte hinzufügen, dass dieser Code die Annahme macht, dass Sie nur eine dieser MyWorkerThreads() ausgeführt haben, sonst würden sie gleichzeitig ausgeführt werden.

1

Ich habe einen Mutex verwendet, wenn ich einzelne Ausführung gewünscht haben:

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

    } 

Sorry, ich bin so spät ... Sie werden den Mutex Namen als Teil des GetMutexName() -Methode zur Verfügung stellen müssen Anruf.