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
For those who are looking for complete example which prevents running tasks concurrently. Based on @Gabriel Luci answer and comments.
Please feel free to comment so I can correct it.
/// <summary>
/// Based on Microsoft.Extensions.Hosting.BackgroundService https://github.com/aspnet/Extensions/blob/master/src/Hosting/Abstractions/src/BackgroundService.cs
/// Additional info: - https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual-studio#timed-background-tasks
/// - https://stackoverflow.com/questions/53844586/async-timer-in-scheduler-background-service
/// </summary>
public abstract class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(ExecuteTask, null, TimeSpan.FromSeconds(30), 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)
{
await RunJobAsync(stoppingToken);
_timer.Change(TimeSpan.FromSeconds(30), TimeSpan.FromMilliseconds(-1));
}
/// <summary>
/// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task
/// </summary>
/// <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(CancellationToken stoppingToken);
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_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();
}
}