using System.Diagnostics; namespace RobotNet.ScriptManager.Services; public abstract class LoopService(int interval) : IHostedService { private readonly TimeSpan Interval = TimeSpan.FromMilliseconds(interval); private readonly CancellationTokenSource cancellationTokenSource = new(); private readonly Stopwatch stopwatch = new(); private Timer? timer; public async Task StartAsync(CancellationToken cancellationToken) { await BeforExecuteAsync(cancellationToken); stopwatch.Start(); timer = new Timer(Callback, cancellationTokenSource, 0, Timeout.Infinite); } public async Task StopAsync(CancellationToken cancellationToken) { cancellationTokenSource.Cancel(); timer?.Dispose(); await AfterExecuteAsync(cancellationToken); } protected virtual Task BeforExecuteAsync(CancellationToken cancellationToken) => Task.CompletedTask; protected abstract void Execute(CancellationToken stoppingToken); protected virtual Task AfterExecuteAsync(CancellationToken cancellationToken) => Task.CompletedTask; private void Callback(object? state) { if (state is not CancellationTokenSource cts || cts.IsCancellationRequested) return; Execute(cts.Token); if (!cts.IsCancellationRequested) { long nextTrigger = Interval.Ticks - (stopwatch.ElapsedTicks % Interval.Ticks); if (nextTrigger <= 0) nextTrigger = Interval.Ticks; int nextIntervalMs = (int)(nextTrigger / TimeSpan.TicksPerMillisecond); timer?.Change(nextIntervalMs, Timeout.Infinite); } } }