Files
XplorePlane/XP.Common/Controls/ImageHistogram/FrameThrottler.cs
T

208 lines
6.8 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
namespace XP.Common.Controls.ImageHistogram
{
/// <summary>
/// 帧率限流器,确保计算频率不超过 MaxFrameRate | Frame rate throttler
/// 支持从任意线程调用,使用 lock 保护内部状态
/// </summary>
internal sealed class FrameThrottler : IDisposable
{
private readonly object _lock = new();
private DateTime _lastProcessTime = DateTime.MinValue;
private Action? _pendingAction;
private CancellationTokenSource? _delayCts;
private bool _isProcessing;
private bool _disposed;
private int _maxFrameRate = 15;
/// <summary>
/// 最大刷新帧率(fps),有效范围 1-60,超出范围自动钳位 | Max frame rate (fps), valid range 1-60, auto-clamped
/// </summary>
public int MaxFrameRate
{
get => _maxFrameRate;
set => _maxFrameRate = Math.Clamp(value, 1, 60);
}
/// <summary>
/// 获取当前帧间隔(毫秒)| Get current frame interval (ms)
/// </summary>
private double FrameIntervalMs => 1000.0 / _maxFrameRate;
/// <summary>
/// 提交一帧计算动作 | Submit a frame compute action
/// 若未超过帧率限制则立即执行,否则缓存最新帧并延迟触发
/// </summary>
/// <param name="computeAction">计算动作 | Compute action</param>
/// <returns>是否被立即接受处理 | Whether it was immediately accepted</returns>
public bool TrySubmit(Action computeAction)
{
if (_disposed || computeAction == null)
return false;
lock (_lock)
{
var now = DateTime.UtcNow;
var elapsed = (now - _lastProcessTime).TotalMilliseconds;
if (elapsed >= FrameIntervalMs && !_isProcessing)
{
// 已超过间隔且无正在处理的任务,立即执行 | Interval exceeded and no processing, execute immediately
_isProcessing = true;
_lastProcessTime = now;
ExecuteAction(computeAction);
return true;
}
else
{
// 未超过间隔或正在处理中,缓存最新帧(丢弃之前的中间帧)| Cache latest frame, discard previous
_pendingAction = computeAction;
ScheduleDelayedExecution(elapsed);
return false;
}
}
}
/// <summary>
/// 执行计算动作(异步,完成后检查待处理帧)| Execute compute action asynchronously
/// </summary>
private void ExecuteAction(Action action)
{
Task.Run(() =>
{
try
{
action.Invoke();
}
catch
{
// 异常不外抛 | Do not propagate exceptions
}
finally
{
OnActionCompleted();
}
});
}
/// <summary>
/// 计算动作完成后的回调 | Callback after compute action completes
/// </summary>
private void OnActionCompleted()
{
Action? nextAction = null;
lock (_lock)
{
_isProcessing = false;
if (_pendingAction != null && !_disposed)
{
var now = DateTime.UtcNow;
var elapsed = (now - _lastProcessTime).TotalMilliseconds;
if (elapsed >= FrameIntervalMs)
{
// 间隔已到,立即执行待处理帧 | Interval reached, execute pending frame
nextAction = _pendingAction;
_pendingAction = null;
_isProcessing = true;
_lastProcessTime = now;
}
// 否则等待延迟触发 | Otherwise wait for delayed trigger
}
}
if (nextAction != null)
{
ExecuteAction(nextAction);
}
}
/// <summary>
/// 安排延迟执行(等待帧间隔到期后处理最新帧)| Schedule delayed execution
/// </summary>
private void ScheduleDelayedExecution(double elapsedMs)
{
// 取消之前的延迟任务 | Cancel previous delay task
_delayCts?.Cancel();
_delayCts?.Dispose();
_delayCts = new CancellationTokenSource();
var token = _delayCts.Token;
var delayMs = Math.Max(0, FrameIntervalMs - elapsedMs);
Task.Run(async () =>
{
try
{
await Task.Delay((int)delayMs, token);
if (token.IsCancellationRequested)
return;
Action? actionToExecute = null;
lock (_lock)
{
if (_pendingAction != null && !_isProcessing && !_disposed)
{
actionToExecute = _pendingAction;
_pendingAction = null;
_isProcessing = true;
_lastProcessTime = DateTime.UtcNow;
}
}
if (actionToExecute != null)
{
ExecuteAction(actionToExecute);
}
}
catch (OperationCanceledException)
{
// 延迟被取消,正常情况 | Delay cancelled, normal case
}
catch
{
// 异常不外抛 | Do not propagate exceptions
}
});
}
/// <summary>
/// 取消所有待处理任务 | Cancel all pending tasks
/// </summary>
public void Cancel()
{
lock (_lock)
{
_pendingAction = null;
_delayCts?.Cancel();
_delayCts?.Dispose();
_delayCts = null;
}
}
/// <summary>
/// 释放所有资源 | Dispose all resources
/// </summary>
public void Dispose()
{
if (_disposed) return;
_disposed = true;
lock (_lock)
{
_pendingAction = null;
_delayCts?.Cancel();
_delayCts?.Dispose();
_delayCts = null;
}
}
}
}