Initial commit

This commit is contained in:
2026-07-03 16:37:12 +07:00
commit 63b8c1ea8b
1931 changed files with 640587 additions and 0 deletions

View File

@@ -0,0 +1,135 @@
using Microsoft.Extensions.Logging;
using System.Diagnostics;
namespace RobotNet10.Common;
public class WatchThread<T>(int Interval, Action Callback, ILogger<T>? Logger, ThreadPriority Priority = ThreadPriority.Highest) : IDisposable where T : class
{
public bool Disposed;
private Thread? Thread;
private CancellationTokenSource? ThreadCts;
private long NextDueTime;
private readonly Lock Lock = new();
private void Handler(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
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("WatchThread 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;
Thread.Sleep((int)delay);
}
}
catch (Exception ex)
{
if (Logger is not null && Logger.IsEnabled(LogLevel.Error)) Logger.LogError("WatchThread Error: {ex}", ex.Message);
Thread.Sleep(Interval);
}
}
}
private static long GetCurrentTimeMs()
{
return Stopwatch.GetTimestamp() * 1000 / Stopwatch.Frequency;
}
public void Start()
{
lock (Lock)
{
if (!Disposed)
{
if (Thread?.IsAlive == true) return;
NextDueTime = GetCurrentTimeMs() + Interval;
ThreadCts = new CancellationTokenSource();
Thread = new Thread(() => Handler(ThreadCts.Token))
{
Priority = Priority,
IsBackground = false,
Name = $"WatchThread-{typeof(T).Name}"
};
Thread.Start();
}
else throw new ObjectDisposedException(nameof(WatchThread<>));
}
}
public void Stop()
{
Thread? threadToJoin;
lock (Lock)
{
if (Thread == null) return;
ThreadCts?.Cancel();
threadToJoin = Thread;
}
// If Stop() is called from within the Callback (same thread), skip Join to avoid deadlock
if (threadToJoin != null && threadToJoin != Thread.CurrentThread)
{
if (!threadToJoin.Join(TimeSpan.FromSeconds(5)))
{
Logger?.LogWarning("Thread did not stop gracefully");
}
}
lock (Lock)
{
ThreadCts?.Dispose();
ThreadCts = null;
Thread = null;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (Disposed) return;
Disposed = true;
if (disposing) Stop();
}
~WatchThread()
{
Dispose(false);
}
}