Question

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

Comment puis-je plus simplement mettre en place une minuterie pour exécuter la tâche toutes les 30 secondes sans jamais les exécutions qui se chevauchent? (Je suppose System.Threading.Timer est la minuterie correcte pour ce travail, mais il pourrait être confondu).

Était-ce utile?

La solution

Vous pouvez le faire avec une minuterie, mais vous devez avoir une certaine forme de verrouillage sur votre analyse de base de données et mise à jour. Un lock simple à synchroniser peut être suffisant pour éviter que plusieurs pistes de se produire.

Cela étant dit, il pourrait être préférable de commencer une minuterie après que vous avez opération est terminée, et juste utiliser une seule fois, puis l'arrêter. Redémarrez après votre prochaine opération. Cela vous donne 30 secondes (ou N secondes) entre les événements, sans risque de chevauchements, et aucun blocage.

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

Travailler immédiatement ..... Fini ... attendez 5 sec .... Travailler immédiatement ..... Fini ... attendez 5 sec ....

Autres conseils

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
}

Je préfère System.Threading.Timer pour des choses comme cela, parce que je ne dois pas passer par le mécanisme de gestion 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 nécessité de la serrure en faisant la minuterie un one-shot et re-démarrage 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);
}

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

Thread updateDBThread = new Thread(MyUpdateMethod);

...

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

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 suit dans le 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;
        }
     }
   }
 }

Chacun de ces 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.

Je l'ai utilisé un mutex quand je voulais 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 en retard ... Vous devrez fournir le nom de mutex dans le cadre de l'appel de méthode GetMutexName ().

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top