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) { _action = action ?? throw new ArgumentNullException(nameof(action)); _cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); _interval = ValidateInterval(interval); Task.Run(ActionLoop); } /// /// Finalizes an instance of the class. /// ~PeriodicTask() { 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 => _interval; set => _interval = ValidateInterval(value); } /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } private void Dispose(bool disposing) { if (disposing) { _cancellationTokenSource.Cancel(); _cancellationTokenSource.Dispose(); } } private static TimeSpan ValidateInterval(TimeSpan value) => value < MinInterval ? MinInterval : value; private async Task ActionLoop() { for (; ; ) { try { await Task.Delay(Interval, _cancellationTokenSource.Token).ConfigureAwait(false); await _action(_cancellationTokenSource.Token).ConfigureAwait(false); } catch (OperationCanceledException) when (_cancellationTokenSource.IsCancellationRequested) { break; } catch (TaskCanceledException) when (_cancellationTokenSource.IsCancellationRequested) { break; } catch (Exception ex) { ex.Log(nameof(PeriodicTask)); } } } } }