I have a task which is supposed to grab some information from an API on each hour. By each hour, I mean literally on each hour, like: 10:00, 11:00, etc. The following code executes DoWork
method on each hour but not on every rounded hour (like 10:00) but 1 hour after the service starts instead.
public class SelfHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public SelfHostedService(ILogger<SelfHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromHours(1));
return Task.CompletedTask;
}
private void DoWork(object state)
{
_logger.LogInformation("Timed Background Service is working.");
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Edit:
public class BotHostedService : IHostedService
{
private readonly ILogger _logger;
private CancellationTokenSource _tokenSource;
private Task _timer;
public BotHostedService(ILogger<BotHostedService> logger)
{
_logger = logger;
}
private DateTime RoundCurrentToNextOneHour()
{
DateTime now = DateTime.Now, result = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0);
return result.AddMinutes(((now.Minute / 60) + 1) * 60);
}
private async Task RunPeriodically(Action action, DateTime startTime, TimeSpan interval, CancellationToken token)
{
DateTime nextRunTime = startTime;
try
{
while (true)
{
TimeSpan delay = nextRunTime - DateTime.Now;
if (delay > TimeSpan.Zero)
{
await Task.Delay(delay, token);
}
action();
nextRunTime += interval;
}
}
catch (OperationCanceledException)
{
return;
}
}
private void DoWork()
{
_logger.LogInformation($"Timed Background Service is working. Current time: {DateTime.Now.ToLocalTime()}");
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
if (_timer == null)
{
_tokenSource = new CancellationTokenSource();
DateTime startTime = RoundCurrentToNextOneHour();
_timer = RunPeriodically(DoWork, startTime, TimeSpan.FromHours(1), _tokenSource.Token);
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
if (_timer != null)
{
_timer = null;
_tokenSource.Cancel();
_tokenSource.Dispose();
_tokenSource = null;
}
return Task.CompletedTask;
}
}