The ASP.NET Core docs for background services show a number of implementation examples.
There's an example for starting a service on a timer, though it's synchronous. There's another example which is asynchronous, for starting a service with a scoped dependency.
I need to do both: start a service every 5 minutes, and it has scoped dependencies. There's no example for that.
I combined both examples, but I'm unsure of a safe way to use Timer
with an async TimerCallback
.
e.g.
public class MyScheduler : IHostedService
{
private Timer? _timer;
private readonly IServiceScopeFactory _serviceScopeFactory;
public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;
public void Dispose() => _timer?.Dispose();
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer((object? state) => {
using var scope = _serviceScopeFactory.CreateScope();
var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
await myService.Execute(cancellationToken); // <------ problem
}), null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) {
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
}
The timer takes a sync callback, so the problem is the await
. What's a safe way to call an async service?
Use BackgroundService
instead of IHostedService
public class MyScheduler : BackgroundService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Option 1
while (!stoppingToken.IsCancellationRequested)
{
// do async work
using (var scope = _serviceScopeFactory.CreateScope())
{
var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
await myService.Execute(stoppingToken);
}
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
// Option 2 (.NET 6)
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
// do async work
// ...as above
}
}
}