How to run BackgroundService on a timer in ASP.NET Core 2.1

S. ten Brinke picture S. ten Brinke · Dec 11, 2018 · Viewed 19.3k times · Source

I want to run a background job in ASP.NET Core 2.1. It has to run every 2 hours and it will need to access my DI Container because it will perform some cleanups in the database. It will need to be async and it should run independently of my ASP.NET Core 2.1 application.

I saw that there was an IHostedService, but ASP.NET Core 2.1 also introduced an abstract class called BackgroundService that does some more work for me. Seems good, I want to use that!

I have not been able to figure out how run a service that derived from BackgroundService on a timer, though.

Do I need to configure this in the ExecuteAsync(token) by remembering the last time it ran and figuring out if this was 2 hours, or is there a better/cleaner way to just say somewhere that it has to run every 2 hours?

Also, is my approach to my problem correct with an BackgroundService?

Thank you!

Edit:

Posted this on the MS extensions repo

Answer

S. ten Brinke picture S. ten Brinke · Jan 17, 2019

Updated 04-2020, read it on the bottom!

@Panagiotis Kanavos gave an answer in the comments of my question but it did not post it as an actual answer; this answer is dedicated to him/her.

I used a Timed background service like the one from Microsoft docs to create the service.

internal class TimedHostedService : IHostedService, IDisposable
{
    private readonly ILogger _logger;
    private Timer _timer;

    public TimedHostedService(ILogger<TimedHostedService> logger)
    {
        _logger = logger;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Timed Background Service is starting.");

        _timer = new Timer(DoWork, null, TimeSpan.Zero, 
            TimeSpan.FromSeconds(5));

        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();
    }
}

In my case I made the _timer call async by doing new Timer(async () => await DoWorkAsync(), ...).

In the future, an extension could be written that makes a class like this available in the Extensions repo because I think this is quite useful. I posted the github issue link in the description.

A tip, if you plan on reusing this class for multiple hosted services, consider creating a base class that contains the timer and an abstract PerformWork() or something so the "time" logic is only in one place.

Thank you for your answers! I hope this helps someone in the future.

Update 04-2020:

Injecting a scoped service in here is not possible with the normal Core service collection DI container, out of the box. I was using autofac which made it possible to use scoped services like IClassRepository in the constructor because of wrong registration, but when I started working on a different project that used only AddScoped<>(), AddSingleton<>(), AddTransient<>() we figured out that injecting scoped things do not work because you are not in a scoped context.

In order to use your scoped services, inject a IServiceScopeFactory (Easier to test with) and use CreateScope() which allows you to use scope.GetService() with a using statement :)