Files
I150/srcs/RobotNet10/Commons/RobotNet10.Common/WatchTimer.cs
2026-07-03 16:37:12 +07:00

113 lines
3.1 KiB
C#

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