I'm writing a hosted service in .Net-Core which runs a job in the background based off of a timer.
Currently I have to code running synchronously like so:
public override Task StartAsync(CancellationToken cancellationToken)
{
this._logger.LogInformation("Timed Background Service is starting.");
this._timer = new Timer(ExecuteTask, null, TimeSpan.Zero,
TimeSpan.FromSeconds(30));
return Task.CompletedTask;
}
private void ExecuteTask(object state)
{
this._logger.LogInformation("Timed Background Service is working.");
using (var scope = _serviceProvider.CreateScope())
{
var coinbaseService = scope.ServiceProvider.GetRequiredService<CoinbaseService>();
coinbaseService.FinalizeMeeting();
}
}
I'd like to run this Async on the timer but I don't want to run async using fire and forget because my it could cause race conditions in my code.
e.g( subscribing to the timer.Elapsed
event)
Is there a way I can leverage asynchronous code on a timed schedule without executing fire and forget
The new default synchronization frequency is 30 minutes. The scheduler is responsible for two tasks: Synchronization cycle. The process to import, sync, and export changes. Maintenance tasks. Renew keys and certificates for Password reset and Device Registration Service (DRS). Purge old entries in the operations log.
The scheduler is responsible for two tasks: Synchronization cycle. The process to import, sync, and export changes. Maintenance tasks. Renew keys and certificates for Password reset and Device Registration Service (DRS).
A timed background task makes use of the System.Threading.Timer class. The timer triggers the task's DoWork method. The timer is disabled on StopAsync and disposed when the service container is disposed on Dispose:
The scheduler is with the 1.1 releases built-in to the sync engine and do allow some customization. The new default synchronization frequency is 30 minutes. The scheduler is responsible for two tasks: Synchronization cycle. The process to import, sync, and export changes. Maintenance tasks.
Here is an improved version based on previous responses. Improvements:
Access to scoped services example
protected override async Task RunJobAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken)
{
DbContext context = serviceProvider.GetRequiredService<DbContext>();
}
Source code:
public abstract class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
IServiceProvider _services;
public TimedHostedService(IServiceProvider services)
{
_services = services;
_logger = _services.GetRequiredService<ILogger<TimedHostedService>>();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(ExecuteTask, null,FirstRunAfter, TimeSpan.FromMilliseconds(-1));
return Task.CompletedTask;
}
private void ExecuteTask(object state)
{
_timer?.Change(Timeout.Infinite, 0);
_executingTask = ExecuteTaskAsync(_stoppingCts.Token);
}
private async Task ExecuteTaskAsync(CancellationToken stoppingToken)
{
try
{
using (var scope = _services.CreateScope())
{
await RunJobAsync(scope.ServiceProvider, stoppingToken);
}
}
catch (Exception exception)
{
_logger.LogError("BackgroundTask Failed", exception);
}
_timer.Change(Interval, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
/// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task
/// </summary>
/// <param name="serviceProvider"></param>
/// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param>
/// <returns>A <see cref="Task"/> that represents the long running operations.</returns>
protected abstract Task RunJobAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken);
protected abstract TimeSpan Interval { get; }
protected abstract TimeSpan FirstRunAfter { get; }
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
// 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 void Dispose()
{
_stoppingCts.Cancel();
_timer?.Dispose();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With