using Microsoft.Extensions.Logging; using System.Diagnostics; namespace RobotNet10.Common; public class WatchTimerAsync(int interval, Func Callback, ILogger? 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); } }