Updated 03-2022, read it on the bottom!
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 :)
Update 03-2022:
This post has gotten LOTS of views and attention, but I have to say I am no longer a big fan of my solution. I would propose different solutions:
- Use hangfire or quartz instead if you want the code to just run in backgroundservice
- take a look at kubernetes cronjobs if you run in a kubernetes environment
- This has the benefit of only running your code when required, saving resources compared to running a project 24/7 and only executing a job every day at 3 AM, for example
- take a look at Azure Functions/AWS Lambda on a timer
- this is probably cheaper and easier to maintain than making your own timed hosted services. It might be more difficult to integrate into a k8s environment, though.
The downsides of the solution posted in this answer are:
- You need to manage a lot of things yourself that the other options do for free. For example:
- What if your app was down when it should have ran the job?
- What if your job takes too long and another one starts?
- Logging and monitoring
- I am still unsure about the
async
support in this solution. I never really figured out if this solution is "correct"
- I also do not like that DI is not supported out of the box.
Quartz.Net
does support this.
- It isn't flexible compared to quartz.