Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Run async hosted service every 5 minutes in ASP.NET Core

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?

like image 485
lonix Avatar asked Sep 14 '25 16:09

lonix


1 Answers

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
        }
    }
}
like image 91
Artur Avatar answered Sep 16 '25 05:09

Artur