using Microsoft.Extensions.Logging; using System.Diagnostics; namespace RobotNet10.Common; public class WatchTimer(int Interval, Action Callback, ILogger? Logger) : IDisposable where T : class { private Timer? Timer; public bool Disposed; private long NextDueTime; private readonly Lock Lock = new(); private 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("WatchTimer Warning: Elapsed time {peak}ms exceeds interval {Interval}ms.", now - scheduledTime + Interval, Interval); } } } if (shouldRun) { try { 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("WatchTimer 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(WatchTimer<>)); } 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; } ~WatchTimer() { Dispose(false); } }