Domanda

sto scrivendo un servizio di Windows che gestisce un'attività di lunghezza variabile ad intervalli (una scansione di database e l'aggiornamento). Ho bisogno di questo compito di eseguire frequentemente, ma il codice per gestire non è sicuro di eseguire più volte contemporaneamente.

Come posso più semplicemente impostare un timer per eseguire l'operazione ogni 30 secondi senza mai sovrapposizione esecuzioni? (Sto assumendo System.Threading.Timer è il timer corretto per questo lavoro, ma potrebbe essere scambiato).

È stato utile?

Soluzione

Si potrebbe fare con un timer, ma si avrebbe bisogno di avere una qualche forma di blocco sulla scansione del database e l'aggiornamento. Un semplice lock per sincronizzare può essere sufficiente per evitare che più piste che si verificano.

Detto questo, potrebbe essere meglio per iniziare un timer dopo sei operazione è completa, e basta usare una sola volta, poi fermarlo. Riavviarlo dopo la prossima operazione. Questo darebbe 30 secondi (o N secondi) tra gli eventi, senza alcuna 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 ....

Altri suggerimenti

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
}

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 la necessità per la serratura, rendendo il timer di un one-shot e ri-partenza 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);
}

al posto di blocco (che potrebbe causare tutte le scansioni a tempo di aspettare e poi impilare). Si potrebbe iniziare la scansione / aggiornamento in un filo e poi basta fare un controllo per vedere se il filo è ancora vivo.

Thread updateDBThread = new Thread(MyUpdateMethod);

...

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

È 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 segue 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 filo, semplicemente segnalando l'evento.


Modifica

Vorrei aggiungere, che questo codice si basa sull'ipotesi che hai solo uno di questi MyWorkerThreads () in esecuzione, altrimenti avrebbero eseguire contemporaneamente.

Ho usato un mutex quando ho voluto singola esecuzione:

    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 di metodo.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top