RaspberryIO_26/Swan.Lite/Threading/PeriodicTask.cs
2019-12-04 18:57:18 +01:00

102 lines
3.4 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)
{
_action = action ?? throw new ArgumentNullException(nameof(action));
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
_interval = ValidateInterval(interval);
Task.Run(ActionLoop);
}
/// <summary>
/// Finalizes an instance of the <see cref="PeriodicTask"/> class.
/// </summary>
~PeriodicTask()
{
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 => _interval;
set => _interval = ValidateInterval(value);
}
/// <inheritdoc />
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));
}
}
}
}
}