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:
public class MyBackgroundService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public MyBackgroundService(ILogger<MyBackgroundService> logger)
{
_logger = logger;
}
public void Dispose()
{
_timer?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan interval = TimeSpan.FromHours(24);
TimeSpan firstCall = DateTime.Today.AddDays(1).AddTicks(-1).Subtract(DateTime.Now);
Action action = () =>
{
Task.Delay(firstCall).Wait();
Process();
_timer = new Timer(
ob => Process(),
null,
TimeSpan.Zero,
interval
);
};
Task.Run(action);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private Task Process()
{
try
{
// perform some database operations
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
}
return Task.CompletedTask;
}
}
This code works as expected. But I don't like that it synchronously waits till calling Process
first time, so a thread is blocked and not performing any useful work (correct me if I am wrong).
I could make an action async and await in it like this:
public Task StartAsync(CancellationToken cancellationToken)
{
// code omitted for brevity
Action action = async () =>
{
await Task.Delay(firstCall);
await Process();
// code omitted for brevity
}
But I am not sure that using Task.Run
is a good thing here as Process
method should perform some I/O operations (query DB and insert some data), and because it's not recommended to use Task.Run
in ASP.NET environment.
I refactored StartAsync
as follows:
public async Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan interval = TimeSpan.FromHours(24);
TimeSpan firstDelay = DateTime.Today.AddDays(1).AddTicks(-1).Subtract(DateTime.Now);
await Task.Delay(firstDelay);
while (!cancellationToken.IsCancellationRequested)
{
await Process();
await Task.Delay(interval, cancellationToken);
}
}
And this allows me not to use timer in MyBackgroundService
at all.
Should I use the first approach with "timer + Task.Run" or the second one with "while loop + Task.Delay"?
Process
take 5 minutes, your schedule will be skewed by 5 minutes on every execution. That is, until your service is restarted. – Newness