114 lines
3.8 KiB
C#
114 lines
3.8 KiB
C#
using Cronos;
|
|
|
|
namespace ApiPolo.Services
|
|
{
|
|
/// <summary></summary>
|
|
public abstract class CronJobService : IHostedService, IDisposable
|
|
{
|
|
private System.Timers.Timer? _timer;
|
|
private readonly CronExpression _expression;
|
|
private readonly TimeZoneInfo _timeZoneInfo;
|
|
|
|
/// <summary></summary>
|
|
protected CronJobService(string cronExpression, TimeZoneInfo timeZoneInfo)
|
|
{
|
|
_expression = CronExpression.Parse(cronExpression);
|
|
_timeZoneInfo = timeZoneInfo;
|
|
}
|
|
|
|
/// <summary></summary>
|
|
public virtual async Task StartAsync(CancellationToken cancellationToken)
|
|
{
|
|
await ScheduleJob(cancellationToken);
|
|
}
|
|
|
|
/// <summary></summary>
|
|
protected virtual async Task ScheduleJob(CancellationToken cancellationToken)
|
|
{
|
|
var next = _expression.GetNextOccurrence(DateTimeOffset.Now, _timeZoneInfo);
|
|
if (next.HasValue)
|
|
{
|
|
var delay = next.Value - DateTimeOffset.Now;
|
|
if (delay.TotalMilliseconds <= 0) // prevent non-positive values from being passed into Timer
|
|
{
|
|
await ScheduleJob(cancellationToken);
|
|
}
|
|
_timer = new System.Timers.Timer(delay.TotalMilliseconds);
|
|
_timer.Elapsed += async (_, _) =>
|
|
{
|
|
_timer.Dispose(); // reset and dispose timer
|
|
_timer = null;
|
|
|
|
if (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
await DoWork(cancellationToken);
|
|
}
|
|
|
|
if (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
await ScheduleJob(cancellationToken); // reschedule next
|
|
}
|
|
};
|
|
_timer.Start();
|
|
}
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary></summary>
|
|
public virtual async Task DoWork(CancellationToken cancellationToken)
|
|
{
|
|
await Task.Delay(5000, cancellationToken); // do the work
|
|
}
|
|
|
|
/// <summary></summary>
|
|
public virtual async Task StopAsync(CancellationToken cancellationToken)
|
|
{
|
|
_timer?.Stop();
|
|
await Task.CompletedTask;
|
|
}
|
|
|
|
/// <summary></summary>
|
|
public virtual void Dispose()
|
|
{
|
|
_timer?.Dispose();
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
}
|
|
|
|
/// <summary></summary>
|
|
public interface IScheduleConfig<T>
|
|
{
|
|
string CronExpression { get; set; }
|
|
TimeZoneInfo TimeZoneInfo { get; set; }
|
|
}
|
|
|
|
/// <summary></summary>
|
|
public class ScheduleConfig<T> : IScheduleConfig<T>
|
|
{
|
|
public string CronExpression { get; set; } = string.Empty;
|
|
public TimeZoneInfo TimeZoneInfo { get; set; } = TimeZoneInfo.Local;
|
|
}
|
|
|
|
/// <summary></summary>
|
|
public static class ScheduledServiceExtensions
|
|
{
|
|
public static IServiceCollection AddCronJob<T>(this IServiceCollection services, Action<IScheduleConfig<T>> options) where T : CronJobService
|
|
{
|
|
if (options == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(options), @"Please provide Schedule Configurations.");
|
|
}
|
|
var config = new ScheduleConfig<T>();
|
|
options.Invoke(config);
|
|
if (string.IsNullOrWhiteSpace(config.CronExpression))
|
|
{
|
|
throw new ArgumentNullException(nameof(ScheduleConfig<T>.CronExpression), @"Empty Cron Expression is not allowed.");
|
|
}
|
|
|
|
services.AddSingleton<IScheduleConfig<T>>(config);
|
|
services.AddHostedService<T>();
|
|
return services;
|
|
}
|
|
}
|
|
}
|