113 lines
3.1 KiB
C#
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);
|
|
}
|
|
}
|