2009-03-26 10 views
16

J'écris un service Windows qui exécute une activité de longueur variable à intervalles réguliers (analyse et mise à jour de la base de données). J'ai besoin de cette tâche pour exécuter fréquemment, mais le code à gérer n'est pas sûr d'exécuter plusieurs fois simultanément.Synchronisation d'un temporisateur pour éviter le chevauchement

Comment puis-je simplement configurer un temporisateur pour exécuter la tâche toutes les 30 secondes sans jamais chevaucher les exécutions? (Je suppose que System.Threading.Timer est la minuterie correcte pour ce travail, mais pourrait être confondue).

Répondre

24

Vous pouvez le faire avec une minuterie, mais vous devez avoir un certain type de verrouillage sur votre base de données scan et mise à jour. Un simple lock de synchronisation peut être suffisant pour empêcher plusieurs exécutions. Cela étant dit, il peut être préférable de démarrer une minuterie APRÈS que vous avez terminé votre opération, et utilisez-la une seule fois, puis arrêtez-la. Redémarrez-le après votre prochaine opération. Cela vous donnerait 30 secondes (ou N secondes) entre les événements, sans risque de chevauchement, et pas de verrouillage.

Exemple:

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

travail immédiatement ..... fini ... attendez 5 sec .... Travailler immédiatement ..... Terminer ... attendre 5 sec ....

+3

Je seconde cette approche - 'il vaudrait peut-être mieux démarrer une minuterie APRÈS que l'opération soit terminée ...' – hitec

+4

Je suis le troisième de cette approche. Vous pouvez également calculer le délai de la minuterie de manière dynamique pour se rapprocher des 30 secondes. Désactivez la minuterie, obtenez l'heure du système, mettez à jour la base de données, puis initialisez la minuterie suivante pour qu'elle se déclenche 30 secondes après l'heure enregistrée. Avec un délai minimum pour la sécurité. – mghie

+0

comment nous pouvons être sûr que l'opération sera complète en 5 secondes. upvote totalise plus que @jsw, mais son aspect est plus efficace. –

20

J'utilise Monitor.TryEnter dans votre code écoulé:

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

au lieu de verrouillage (qui pourrait faire attendre et éventuellement empiler tous vos scans chronométrés). Vous pouvez démarrer l'analyse/la mise à jour dans un thread, puis effectuer une vérification pour voir si le thread est toujours actif.

Thread updateDBThread = new Thread(MyUpdateMethod); 

...

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

Rien à voir avec le déclenchement d'un événement asynchrone pour démarrer un thread. –

+0

True, mais si vous avez exécuté l'analyse/mise à jour dans le délai écoulé, vous ne pouvez pas obtenir un handle sur elle pour vérifier si elle était en vie. –

11

Je préfère System.Threading.Timer pour des choses comme cela, parce que je ne dois pas passer par le mécanisme de traitement des événements:

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

Vous pouvez éliminer la besoin de la serrure en faisant de la minuterie un one-shot et de le redémarrer après chaque mise à jour:

// 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 créant plusieurs threads. – RollerCosta

1

Vous pouvez utiliser le AutoResetEvent comme suit:

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

Une solution légèrement plus "intelligents" est comme suivre pseudo-code:

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

Chacune de ces derniers a l'avantage que vous pouvez arrêter le fil, juste en signalant l'événement.


Modifier

Je dois ajouter que ce code fait l'hypothèse que vous avez seulement un de ces MyWorkerThreads() Course à pied, sinon ils courent en même temps.

1

Je l'ai utilisé un mutex quand j'ai voulu l'exécution simple:

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

    } 

Désolé, je suis si tard ... Vous devrez fournir le nom de mutex dans le cadre de la méthode GetMutexName() appel.