86 lines
3.1 KiB
C#
86 lines
3.1 KiB
C#
using System;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using Swan.Logging;
|
|
|
|
namespace Swan.Threading {
|
|
/// <summary>
|
|
/// Schedule an action to be periodically executed on the thread pool.
|
|
/// </summary>
|
|
public sealed class PeriodicTask : IDisposable {
|
|
/// <summary>
|
|
/// <para>The minimum interval between action invocations.</para>
|
|
/// <para>The value of this field is equal to 100 milliseconds.</para>
|
|
/// </summary>
|
|
public static readonly TimeSpan MinInterval = TimeSpan.FromMilliseconds(100);
|
|
|
|
private readonly Func<CancellationToken, Task> _action;
|
|
private readonly CancellationTokenSource _cancellationTokenSource;
|
|
|
|
private TimeSpan _interval;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="PeriodicTask"/> class.
|
|
/// </summary>
|
|
/// <param name="interval">The interval between invocations of <paramref name="action"/>.</param>
|
|
/// <param name="action">The callback to invoke periodically.</param>
|
|
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel operations.</param>
|
|
public PeriodicTask(TimeSpan interval, Func<CancellationToken, Task> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Finalizes an instance of the <see cref="PeriodicTask"/> class.
|
|
/// </summary>
|
|
~PeriodicTask() {
|
|
this.Dispose(false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// <para>Gets or sets the interval between periodic action invocations.</para>
|
|
/// <para>Changes to this property take effect after next action invocation.</para>
|
|
/// </summary>
|
|
/// <seealso cref="MinInterval"/>
|
|
public TimeSpan Interval {
|
|
get => this._interval;
|
|
set => this._interval = ValidateInterval(value);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|