JoshRivers
JoshRivers

Reputation: 10290

Synchronizing a timer to prevent overlap

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't safe to run multiple times concurrently.

How can I most simply set up a timer to run the task every 30 seconds while never overlapping executions? (I'm assuming System.Threading.Timer is the correct timer for this job, but could be mistaken).

Upvotes: 30

Views: 26482

Answers (8)

broadband
broadband

Reputation: 3498

What is wrong with this, no locks, no monitors:

while(true)
{
  if (stopProcessingCondition)
  {
    break;
  }

  // let's say our interval is 15 seconds
  Stopwatch stopWatch = new Stopwatch();
  stopWatch.Start();

  // do the job

  stopWatch.Stop();

  int elapsedMilliseconds= stopWatch.ElapsedMilliseconds;
  if (elapsedMilliseconds> 15)
  {
    continue;
  }
  else
  {
    Thread.Sleep(15000 - elapsedMilliseconds);
  }
}

It block the main thread, but this can easily be solved like this:

Task task = Task.Run(() =>
{
  while(true)
  {
    // do the job
    Thread.Sleep(1500);
  }
});

Upvotes: 0

Reed Copsey
Reed Copsey

Reputation: 564631

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

Upvotes: 40

Theodor Zoulias
Theodor Zoulias

Reputation: 43738

Starting from .NET 6 there is a new timer available, the PeriodicTimer. This is a lightweight async-enabled timer, that becomes the perfect tool when overlapping executions should be strictly forbidden. You use this timer by writing an asynchronous method with a loop, and 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 using a CancellationTokenSource, you can also stop the loop by disposing the PeriodicTimer. In this case the await timer.WaitForNextTickAsync() will return false.

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

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

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

Upvotes: 5

sscheider
sscheider

Reputation: 532

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.

Upvotes: 1

Jim Mischel
Jim Mischel

Reputation: 134035

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

Upvotes: 19

grieve
grieve

Reputation: 13508

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.

Upvotes: 1

Steven Evers
Steven Evers

Reputation: 17206

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

Upvotes: 2

jsw
jsw

Reputation: 1782

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
}

Upvotes: 30

Related Questions