115 lines
3.2 KiB
C#
115 lines
3.2 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using System.Diagnostics;
|
|
|
|
namespace RobotNet10.Common;
|
|
|
|
public class WatchTimerAsync<T>(int interval, Func<Task> Callback, ILogger<T>? Logger) : IDisposable where T : class
|
|
{
|
|
private Timer? Timer;
|
|
public bool Disposed;
|
|
|
|
private long NextDueTime;
|
|
private readonly Lock Lock = new();
|
|
|
|
public int Interval => interval;
|
|
|
|
private async void Handler(object? state)
|
|
{
|
|
try
|
|
{
|
|
bool shouldRun = false;
|
|
lock (Lock)
|
|
{
|
|
if (Disposed) return;
|
|
long now = GetCurrentTimeMs();
|
|
if (now >= NextDueTime)
|
|
{
|
|
shouldRun = true;
|
|
long scheduledTime = NextDueTime;
|
|
NextDueTime += interval;
|
|
|
|
if (now - scheduledTime > interval / 2)
|
|
{
|
|
NextDueTime = now + interval;
|
|
if (Logger is not null && Logger.IsEnabled(LogLevel.Warning)) Logger.LogWarning("WatchTimerAsync Warning: Elapsed time {peak}ms exceeds interval {interval}ms.", now - scheduledTime + interval, interval);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (shouldRun)
|
|
{
|
|
try { await Callback.Invoke(); }
|
|
catch (Exception ex) { if (Logger is not null && Logger.IsEnabled(LogLevel.Error)) Logger.LogError("Callback error: {ex}", ex.Message); }
|
|
}
|
|
|
|
lock (Lock)
|
|
{
|
|
if (Disposed) return;
|
|
long now = GetCurrentTimeMs();
|
|
long delay = NextDueTime - now;
|
|
if (delay < 0) delay = 0;
|
|
Timer?.Change(delay, Timeout.Infinite);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
if (Logger is not null && Logger.IsEnabled(LogLevel.Error)) Logger.LogError("WatchTimerAsync Error: {ex}", ex.Message);
|
|
Timer?.Change(interval, Timeout.Infinite);
|
|
}
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
if (!Disposed)
|
|
{
|
|
lock (Lock)
|
|
{
|
|
NextDueTime = GetCurrentTimeMs() + interval;
|
|
Timer = new Timer(Handler, null, Timeout.Infinite, Timeout.Infinite);
|
|
Timer.Change(interval, Timeout.Infinite);
|
|
}
|
|
}
|
|
else throw new ObjectDisposedException(nameof(WatchTimerAsync<>));
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (Disposed) return;
|
|
|
|
if (Timer != null)
|
|
{
|
|
lock (Lock)
|
|
{
|
|
Timer.Change(Timeout.Infinite, Timeout.Infinite);
|
|
Timer.Dispose();
|
|
Timer = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static long GetCurrentTimeMs()
|
|
{
|
|
return Stopwatch.GetTimestamp() * 1000 / Stopwatch.Frequency;
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (Disposed) return;
|
|
|
|
if (disposing) Stop();
|
|
|
|
Disposed = true;
|
|
}
|
|
|
|
~WatchTimerAsync()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
}
|