I have a requirement that background service should run Process
method every day at 0:00 a.m.
So, one of my team member wrote the following code:
The while
loop approach is simpler and safer. Using the Timer class has two hidden gotchas:
Your current while
loop implementation can be improved in various ways though:
DateTime.Now
multiple times during a TimeSpan
calculation may produce unexpected results, because the DateTime
returned by DateTime.Now
can be different each time. It is preferable to store the DateTime.Now
in a variable, and use the stored value in the calculations.cancellationToken.IsCancellationRequested
in the while
loop could result to inconsistent cancellation behavior, if you also use the same token as an argument of the Task.Delay
. Skipping this check completely is simpler and consistent. This way cancelling the token will always produce an OperationCanceledException
as a result.Process
should not affect the scheduling of the next operation. One way to do it is to create the Task.Delay
task before starting the Process
, and await
it after the completion of the Process
. Or you can just recalculate the next delay based on the current time. This has also the advantage that the scheduling will be adjusted automatically in case of a system-wise time change.Here is my suggestion:
public async Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan scheduledTime = TimeSpan.FromHours(0); // midnight
TimeSpan minimumIntervalBetweenStarts = TimeSpan.FromHours(12);
while (true)
{
var scheduledDelay = scheduledTime - DateTime.Now.TimeOfDay;
while (scheduledDelay < TimeSpan.Zero)
scheduledDelay += TimeSpan.FromDays(1);
await Task.Delay(scheduledDelay, cancellationToken);
var delayBetweenStarts =
Task.Delay(minimumIntervalBetweenStarts, cancellationToken);
await ProcessAsync();
await delayBetweenStarts;
}
}
The reason for the minimumIntervalBetweenStarts
is to protect from very dramatic system-wise time changes.