using System; using System.Threading; using xCommons.Extensions; using xExceptions.Constants; using System.Threading.Tasks; using Microsoft.Extensions.Logging; namespace xCommons.Providers { /// /// Do Background Jobs based on Time Intervals ... /// public abstract class XTimeBasedBackgroundService : XBackgroundService { // private bool isWorking = false; private readonly TimeSpan restartInterval; private readonly SemaphoreSlim mlock = new(1, 1); public DateTime? LastRunTime { get; private set; } private CancellationTokenSource cancellationTokenSource; protected XTimeBasedBackgroundService( IServiceProvider serviceProvider, ILogger logger, int intervalInMinutes = 10 ) : base( logger: logger, serviceProvider: serviceProvider ) { // // Validate Interval ... if (intervalInMinutes <= 0) { XException.InvalidArgs.Throw(); } // restartInterval = TimeSpan.FromMinutes(intervalInMinutes); } /// /// Override base action to Implement timing ... /// /// /// protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // while (!stoppingToken.IsCancellationRequested) { try { if (!isWorking) { // // Enable Working ... await mlock.WaitAsync(stoppingToken); try { isWorking = true; } finally { mlock.Release(); } // // Start a new work task ... cancellationTokenSource = CancellationTokenSource .CreateLinkedTokenSource(stoppingToken); // // Execute the work ... await ExecuteWorkAsync(cancellationTokenSource.Token); // // Disable Working ... await mlock.WaitAsync(stoppingToken); try { isWorking = false; } finally { mlock.Release(); } // // Wait for the restart interval ... await Task.Delay(restartInterval, stoppingToken); } else { // // Prevent tight error loops ... await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); } } catch (OperationCanceledException) { // // Disable Working ... await mlock.WaitAsync(stoppingToken); try { isWorking = false; } finally { mlock.Release(); } break; } catch (Exception) { // // Disable Working ... await mlock.WaitAsync(stoppingToken); try { isWorking = false; } finally { mlock.Release(); } // // Prevent tight error loops ... await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); } } } /// /// Override Stop Background Worker ... /// /// /// public override async Task StopAsync(CancellationToken cancellationToken) { // // Cancel the current work session cancellationTokenSource.Cancel(); await base.StopAsync(cancellationToken); } /// /// Dispose all Objects ... /// public override void Dispose() { mlock.Dispose(); cancellationTokenSource.Dispose(); GC.SuppressFinalize(this); base.Dispose(); } /// /// do all required works here ... /// /// /// public abstract Task DoWorkAsync( CancellationToken cancellationToken = default ); /// /// Refresh Worker ... /// /// /// public async Task Refresh( CancellationToken cancellationToken = default ) { // // Cancel current work if running cancellationTokenSource.Cancel(); // // Wait a moment for cancellation await Task.Delay(100, cancellationToken); // // Start new work immediately cancellationTokenSource = CancellationTokenSource .CreateLinkedTokenSource(cancellationToken); await ExecuteWorkAsync(cancellationTokenSource.Token); } // #region Private ... private async Task ExecuteWorkAsync(CancellationToken cancellationToken) { // LastRunTime = DateTime.UtcNow; try { await DoWorkAsync(cancellationToken); } catch (OperationCanceledException) { } catch (Exception) { } finally { } } #endregion } }