It’s natural to exploit the relative new feature of ASPNET 2.0 called IHostedService to schedule background async tasks.
In order to start, the implementation of the interface needs to be added to services:
1 2 3 4 5 6 7 8 9 |
public class Startup { ... public void ConfigureServices(IServiceCollection services) { ... services.AddSingleton<IHostedService, S4y.EchocastAggregator.Sync.Cron>(); } } |
Since this ASPNET will manage background thread life cycle calling StartAsync and StopAsync methods of the service. Those methods are to accept and handle CancellationToken parameter in order to gracefully complete the tasks and can be implemented in pretty simple code. Since next 2.1 version of ASPNET there will be an abstract class to implement this interface and make the things even simpler, so it’s worth to borrow that class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
public abstract class BackgroundAbstractService : IHostedService, IDisposable { private Task _executingTask; private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource(); protected abstract Task ExecuteAsync(CancellationToken stoppingToken); public virtual Task StartAsync(CancellationToken cancellationToken) { // Store the task we're executing _executingTask = ExecuteAsync(_stoppingCts.Token); // If the task is completed then return it, // this will bubble cancellation and failure to the caller if (_executingTask.IsCompleted) { return _executingTask; } // Otherwise it's running return Task.CompletedTask; } public virtual async Task StopAsync(CancellationToken cancellationToken) { // Stop called without start if (_executingTask == null) { return; } try { // Signal cancellation to the executing method _stoppingCts.Cancel(); } finally { // Wait until the task completes or the stop token triggers await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken)); } } public virtual void Dispose() { _stoppingCts.Cancel(); } } |
Now we have to extend this abstract class and implement exactly one method:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
public class Cron : BackgroundAbstractService { private readonly ILogger<Cron> _logger; public Cron(ILogger<Cron> logger) { _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogDebug($"CronService is starting."); while (!stoppingToken.IsCancellationRequested) { _logger.LogDebug($"CronService task doing background work."); await Task.Delay(5000, stoppingToken); } _logger.LogDebug($"CronService background task is stopping."); } } |
Build, run, see log messages every 5 seconds, enjoy. Very simple.