[ACCEPTED]-Synchronizing a timer to prevent overlap-overlap
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....
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
}
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);
}
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();
}
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.
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.
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
We use cookies to improve the performance of the site. By staying on our site, you agree to the terms of use of cookies.