using System; using System.Threading; using System.Threading.Tasks; using Swan.Logging; namespace Swan.Threading { /// /// Schedule an action to be periodically executed on the thread pool. /// public sealed class PeriodicTask : IDisposable { /// /// The minimum interval between action invocations. /// The value of this field is equal to 100 milliseconds. /// public static readonly TimeSpan MinInterval = TimeSpan.FromMilliseconds(100); private readonly Func _action; private readonly CancellationTokenSource _cancellationTokenSource; private TimeSpan _interval; /// /// Initializes a new instance of the class. /// /// The interval between invocations of . /// The callback to invoke periodically. /// A that can be used to cancel operations. public PeriodicTask(TimeSpan interval, Func action, CancellationToken cancellationToken = default) { this._action = action ?? throw new ArgumentNullException(nameof(action)); this._cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); this._interval = ValidateInterval(interval); _ = Task.Run(this.ActionLoop); } /// /// Finalizes an instance of the class. /// ~PeriodicTask() { this.Dispose(false); } /// /// Gets or sets the interval between periodic action invocations. /// Changes to this property take effect after next action invocation. /// /// public TimeSpan Interval { get => this._interval; set => this._interval = ValidateInterval(value); } /// public void Dispose() { this.Dispose(true); GC.SuppressFinalize(this); } private void Dispose(Boolean disposing) { if(disposing) { this._cancellationTokenSource.Cancel(); this._cancellationTokenSource.Dispose(); } } private static TimeSpan ValidateInterval(TimeSpan value) => value < MinInterval ? MinInterval : value; private async Task ActionLoop() { for(; ; ) { try { await Task.Delay(this.Interval, this._cancellationTokenSource.Token).ConfigureAwait(false); await this._action(this._cancellationTokenSource.Token).ConfigureAwait(false); } catch(OperationCanceledException) when(this._cancellationTokenSource.IsCancellationRequested) { break; } catch(TaskCanceledException) when(this._cancellationTokenSource.IsCancellationRequested) { break; } catch(Exception ex) { ex.Log(nameof(PeriodicTask)); } } } } }