Synchronizing a timer to prevent overlap

后端 未结 6 2202
谎友^
谎友^ 2020-12-08 21:02

I\'m writing a Windows service that runs a variable length activity at intervals (a database scan and update). I need this task to run frequently, but the code to handle isn

相关标签:
6条回答
  • 2020-12-08 21:35

    You could do it with a Timer, but you would need to have some form of locking on your database scan and update. A simple lock to synchronize may be enough to prevent multiple runs from occurring.

    That being said, it might be better to start a timer AFTER you're operation is complete, and just use it one time, then stop it. Restart it after your next operation. This would give you 30 seconds (or N seconds) between events, with no chance of overlaps, and no locking.

    Example :

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

    Work immediately .....Finish...wait 5 sec....Work immediately .....Finish...wait 5 sec....

    0 讨论(0)
  • 2020-12-08 21:36

    I'd use Monitor.TryEnter in your elapsed code:

    if (Monitor.TryEnter(lockobj))
    {
      try
      {
        // we got the lock, do your work
      }
      finally
      {
         Monitor.Exit(lockobj);
      }
    }
    else
    {
      // another elapsed has the lock
    }
    
    0 讨论(0)
  • 2020-12-08 21:36

    You could use the AutoResetEvent as follows:

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

    A slightly "smarter" solution is as follow in 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;
            }
         }
       }
     }
    

    Either of these has the advantage that you can shutdown the thread, just by signaling the event.


    Edit

    I should add, that this code makes the assumption that you only have one of these MyWorkerThreads() running, otherwise they would run concurrently.

    0 讨论(0)
  • 2020-12-08 21:53

    I've used a mutex when I've wanted single execution:

        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 I'm so late...You will need to provide the mutex name as part of the GetMutexName() method call.

    0 讨论(0)
  • 2020-12-08 21:55

    instead of locking (which could cause all of your timed scans to wait and eventually stack up). You could start the scan/update in a thread and then just do a check to see if the thread is still alive.

    Thread updateDBThread = new Thread(MyUpdateMethod);
    

    ...

    private void timer_Elapsed(object sender, ElapsedEventArgs e)
    {
        if(!updateDBThread.IsAlive)
            updateDBThread.Start();
    }
    
    0 讨论(0)
  • 2020-12-08 21:56

    I prefer System.Threading.Timer for things like this, because I don't have to go through the event handling mechanism:

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

    You can eliminate the need for the lock by making the timer a one-shot and re-starting it after every update:

    // 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 讨论(0)
提交回复
热议问题