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.
DoWork()
is not async. I could markDoWork
async, but that is not really the correct way because it won't be awaited (?) – PyromaniaIHostedService
and then using a timer would be better than usingBackgroundService
and checking if you want to run your work in theExecuteAsync
by doing a timer check? (Again,post your answer + reasons why this is better than this approach as an answer) I get that my approach would lead toExecuteAsync
being called if it is not going to be executed, but then my question becomes: What is the point ofBackgroundService
if you can't put it on a timer? Followed up by: Why is there not aTimedBackgroundService
then? – PyromaniaTimer: var timer = new System.Threading.Timer(async (e) => { await Task.Delay(500); Console.WriteLine("Tick"); }, null, 0, 5000);
That thread says you'd need to do a try/catch to manage exceptions though.If an unhandled one occurs, the timer could stop working. Again, this all should be brought as an actual answer to the question so it's easier to discuss. – Pyromania