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 asyncsupport 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.Netdoes support this.
- It isn't flexible compared to quartz.