[ACCEPTED]-Synchronizing a timer to prevent overlap-overlap

Accepted answer
Score: 38

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

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

Example 1 :

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

Score: 29

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

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

I prefer System.Threading.Timer for things like this, because 4 I don't have to go through the event handling 3 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 2 the lock by making the timer a one-shot 1 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);
}
Score: 2

instead of locking (which could cause all 4 of your timed scans to wait and eventually 3 stack up). You could start the scan/update 2 in a thread and then just do a check to 1 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();
}
Score: 2

Starting from .NET 6 there is a new timer 19 available, the PeriodicTimer. This is a lightweight async-enabled 18 timer, that becomes the perfect tool when 17 overlapping executions should be strictly 16 forbidden. You use this timer by writing 15 an asynchronous method with a loop, and 14 invoking it to start the loop:

private Task _operation;
private CancellationTokenSource _operationCancellation = new();

//...
_operation = StartTimer();
//...

private async Task StartTimer()
{
    PeriodicTimer timer = new(TimeSpan.FromSeconds(30));
    while (true)
    {
        await timer.WaitForNextTickAsync(_operationCancellation.Token);
        try
        {
            DoSomething();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex);
        }
    }
}

Instead of 13 using a CancellationTokenSource, you can also stop the loop by 12 disposing the PeriodicTimer. In this case the await timer.WaitForNextTickAsync() will return false.

It 11 is possible that the DoSomething will be invoked subsequently 10 with smaller interval than 30 seconds, but 9 it's impossible that it will be invoked 8 in overlapping fashion, unless you start 7 accidentally two asynchronous loops.

This 6 timer does not support disabling and reenabling 5 it. If you need this functionality you could 4 look at the third-party Nito.AsyncEx.PauseTokenSource component.

In case 3 you are targeting a .NET version earlier 2 than .NET 6, you could look at this question 1 for an alternative: Run async method regularly with specified interval.

Score: 1

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 7 slightly "smarter" solution is as follow 6 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 5 that you can shutdown the thread, just by 4 signaling the event.


Edit

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

Score: 1

I've used a mutex when I've wanted single 3 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 2 to provide the mutex name as part of the 1 GetMutexName() method call.

More Related questions