158 lines
4.2 KiB
C#
158 lines
4.2 KiB
C#
using System.Diagnostics;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace RobotNet10.RobotApp.Services.Robot;
|
|
|
|
internal static partial class Windows
|
|
{
|
|
[LibraryImport("winmm.dll")]
|
|
internal static partial uint timeBeginPeriod(uint uPeriod);
|
|
|
|
[LibraryImport("winmm.dll")]
|
|
internal static partial uint timeEndPeriod(uint uPeriod);
|
|
}
|
|
|
|
public static class HighPrecisionTimerHelper
|
|
{
|
|
public static void EnableHighPrecision()
|
|
{
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
_ = Windows.timeBeginPeriod(2);
|
|
}
|
|
}
|
|
|
|
public static void DisableHighPrecision()
|
|
{
|
|
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
|
{
|
|
_ = Windows.timeEndPeriod(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
public class HighPrecisionTimer<T>(int Interval, Action Callback, Logger<T>? Logger) : IDisposable where T : class
|
|
{
|
|
public bool Disposed;
|
|
private Thread? Thread;
|
|
private long IntervalTicks;
|
|
private long NextDueTime;
|
|
private readonly Lock Lock = new();
|
|
|
|
private void Handler()
|
|
{
|
|
while (!Disposed)
|
|
{
|
|
long now = Stopwatch.GetTimestamp();
|
|
|
|
bool shouldRun = false;
|
|
|
|
lock (Lock)
|
|
{
|
|
if (Disposed) break;
|
|
if (now >= NextDueTime)
|
|
{
|
|
shouldRun = true;
|
|
long scheduledTime = NextDueTime;
|
|
NextDueTime += IntervalTicks;
|
|
|
|
// Tự đồng bộ nếu lệch quá
|
|
long driftTicks = now - scheduledTime;
|
|
if (driftTicks > IntervalTicks / 2)
|
|
{
|
|
Logger?.Warning($"High-res timer drift: {driftTicks * 1000.0 / Stopwatch.Frequency:F3}ms. Resync.");
|
|
NextDueTime = now + IntervalTicks;
|
|
}
|
|
}
|
|
}
|
|
|
|
// === BƯỚC 2: Chạy callback ===
|
|
if (shouldRun)
|
|
{
|
|
try
|
|
{
|
|
Callback.Invoke();
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Logger?.Error($"Callback error in high-precision timer: {ex}");
|
|
}
|
|
}
|
|
|
|
// === BƯỚC 3: Chờ chính xác đến lần sau ===
|
|
long sleepUntil = NextDueTime;
|
|
while (!Disposed)
|
|
{
|
|
now = Stopwatch.GetTimestamp();
|
|
long remaining = sleepUntil - now;
|
|
|
|
if (remaining <= 0)
|
|
break;
|
|
|
|
// > 1ms → Sleep
|
|
if (remaining > Stopwatch.Frequency / 1000)
|
|
{
|
|
Thread.Sleep(1);
|
|
}
|
|
// < 1ms → SpinWait
|
|
else
|
|
{
|
|
Thread.SpinWait((int)(remaining / 10));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
if (!Disposed)
|
|
{
|
|
lock (Lock)
|
|
{
|
|
if (Interval < 30) HighPrecisionTimerHelper.EnableHighPrecision();
|
|
IntervalTicks = (long)(Interval * (Stopwatch.Frequency / 1000.0));
|
|
Thread = new Thread(Handler) { IsBackground = true, Priority = ThreadPriority.Highest };
|
|
NextDueTime = Stopwatch.GetTimestamp() + IntervalTicks;
|
|
Thread.Start();
|
|
}
|
|
}
|
|
else throw new ObjectDisposedException(nameof(HighPrecisionTimer<T>));
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (Disposed) return;
|
|
|
|
if (Thread != null)
|
|
{
|
|
Disposed = true;
|
|
lock (Lock)
|
|
{
|
|
Thread.Join(100);
|
|
Thread = null;
|
|
HighPrecisionTimerHelper.DisableHighPrecision();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
Dispose(true);
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
{
|
|
if (Disposed) return;
|
|
|
|
if (disposing) Stop();
|
|
|
|
Disposed = true;
|
|
}
|
|
|
|
~HighPrecisionTimer()
|
|
{
|
|
Dispose(false);
|
|
}
|
|
}
|