Files
xCommons/Providers/XTimeBasedBackgroundService.cs
T

215 lines
6.5 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using xCommons.Extensions;
using xExceptions.Constants;
namespace xCommons.Providers
{
/// <summary>
/// Do Background Jobs based on Time Intervals ...
/// </summary>
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<XTimeBasedBackgroundService> logger,
int intervalInMinutes = 10
) : base(
logger: logger,
serviceProvider: serviceProvider
)
{
//
// Validate Interval ...
if (intervalInMinutes <= 0)
{
XException.InvalidArgs.Throw();
}
//
restartInterval = TimeSpan.FromMinutes(intervalInMinutes);
}
/// <summary>
/// Override base action to Implement timing ...
/// </summary>
/// <param name="stoppingToken"></param>
/// <returns></returns>
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);
}
}
}
/// <summary>
/// Override Stop Background Worker ...
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public override async Task StopAsync(CancellationToken cancellationToken)
{
//
// Cancel the current work session
cancellationTokenSource.Cancel();
await base.StopAsync(cancellationToken);
}
/// <summary>
/// Dispose all Objects ...
/// </summary>
public override void Dispose()
{
mlock.Dispose();
cancellationTokenSource.Dispose();
GC.SuppressFinalize(this);
base.Dispose();
}
/// <summary>
/// do all required works here ...
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public abstract Task DoWorkAsync(
CancellationToken cancellationToken = default
);
/// <summary>
/// Refresh Worker ...
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
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
}
}