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));
}
}
}
}
}