Files
Ayay/SHH.CameraSdk/Core/Memory/FramePool.cs

191 lines
6.7 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using Ayay.SerilogLogs;
using OpenCvSharp;
using Serilog;
namespace SHH.CameraSdk;
/// <summary>
/// [零延迟核心] 智能帧对象池
/// 功能:预分配并复用 SmartFrame 实例,杜绝频繁 new Mat() 与 GC 回收,消除内存分配停顿
/// 核心策略:
/// <para>1. 预热分配:启动时创建初始数量帧,避免运行时内存申请</para>
/// <para>2. 上限控制:最大池大小限制内存占用,防止内存溢出</para>
/// <para>3. 背压丢帧:池空时返回 null强制丢帧保证实时性不阻塞生产端</para>
/// </summary>
public class FramePool : IDisposable
{
private ILogger _sdkLog => Log.ForContext("SourceContext", LogModules.HikVisionSdk);
#region --- (Private Resources & Configurations) ---
/// <summary> 可用帧队列(线程安全):存储待借出的空闲智能帧 </summary>
private readonly ConcurrentQueue<SmartFrame> _availableFrames = new();
/// <summary> 所有已分配帧列表:用于统一销毁释放内存 </summary>
private readonly List<SmartFrame> _allAllocatedFrames = new();
/// <summary> 创建新帧锁:确保多线程下创建新帧的线程安全 </summary>
private readonly object _lock = new();
/// <summary> 帧宽度(与相机输出分辨率一致) </summary>
private readonly int _width;
/// <summary> 帧高度(与相机输出分辨率一致) </summary>
private readonly int _height;
/// <summary> 帧数据类型(如 CV_8UC3 对应 RGB 彩色图像) </summary>
private readonly MatType _type;
/// <summary> 池最大容量:限制最大分配帧数,防止内存占用过高 </summary>
private readonly int _maxPoolSize;
#endregion
#region --- (Constructor & Warm-Up) ---
/// <summary>
/// 初始化智能帧对象池
/// </summary>
/// <param name="width">帧宽度</param>
/// <param name="height">帧高度</param>
/// <param name="type">帧数据类型</param>
/// <param name="initialSize">初始预热帧数默认5</param>
/// <param name="maxSize">池最大容量默认10</param>
public FramePool(int width, int height, MatType type, int initialSize = 5, int maxSize = 10)
{
_width = width;
_height = height;
_type = type;
_maxPoolSize = maxSize;
// 预热:启动时预分配初始数量帧,避免运行时动态申请内存
for (int i = 0; i < initialSize; i++)
{
CreateNewFrame();
}
}
/// <summary>
/// 创建新智能帧并加入池(内部调用,加锁保护)
/// </summary>
private void CreateNewFrame()
{
var frame = new SmartFrame(this, _width, _height, _type);
_allAllocatedFrames.Add(frame);
_availableFrames.Enqueue(frame);
}
#endregion
#region --- (Frame Borrow & Return) ---
/// <summary>
/// 从池借出一个智能帧O(1) 时间复杂度)
/// </summary>
/// <returns>可用智能帧 / 池空且达上限时返回 null触发背压丢帧</returns>
public SmartFrame? Get()
{
// 1. 优先从可用队列取帧,无锁快速路径
if (_availableFrames.TryDequeue(out var frame))
{
frame.Activate();
frame.MarkBorrowed(); // 记录起始时间
return frame;
}
// 2. 可用队列为空,检查是否达最大容量
if (_allAllocatedFrames.Count < _maxPoolSize)
{
// 加锁创建新帧,避免多线程重复创建
lock (_lock)
{
// 双重检查:防止等待锁期间其他线程已创建新帧
if (_allAllocatedFrames.Count < _maxPoolSize)
{
CreateNewFrame();
}
}
// 递归重试取帧
return Get();
}
// ============================================================
// 3. [自愈触发] 如果走到这里,说明池子满了且所有帧都在外借中。
// 可能存在“僵尸帧”死锁。执行强制回收哨兵。
// ============================================================
if (ForceRecycleZombies())
{
// 如果哨兵成功救回了至少一帧,递归重试就能拿到帧
return Get();
}
// 3. 背压策略:池空且达上限,返回 null 强制丢帧,保证生产端不阻塞
// 适用场景:消费端处理过慢导致帧堆积,丢帧保实时性
return null;
}
/// <summary>
/// 哨兵巡检:强制回收占用超过 5 秒不还的僵尸帧
/// </summary>
private bool ForceRecycleZombies()
{
bool anyRescued = false;
long now = Environment.TickCount64;
// Optimized: [原因] 使用 lock 或 ToArray() 防止在哨兵巡检期间,
// 其他线程触发 CreateNewFrame 导致 List 集合修改异常。
SmartFrame[] snapshot;
lock (_lock) { snapshot = _allAllocatedFrames.ToArray(); }
// 遍历所有已分配的帧,找出超时的僵尸
foreach (var frame in snapshot)
{
// 条件当前不在池中_refCount > 0且借出时间超过 2000ms
// 注意:这里需要给 SmartFrame 暴露一个只读的 RefCount 属性,或者直接判断 BorrowedTick
if ((now - frame.BorrowedTick) > 2000)
{
// 强行重置该帧的所有权
frame.ForceReset();
// 重新塞回队列
_availableFrames.Enqueue(frame);
anyRescued = true;
// 记录一条警告日志,告诉你哪路视频出问题了
// 可以在 AddAuditLog 里看到
_sdkLog.Warning("[Sdk] SmartFrame(借出超2秒) 被强制回收.");
}
}
return anyRescued;
}
/// <summary>
/// [系统内部调用] 将帧归还至池(由 SmartFrame.Dispose 自动触发)
/// </summary>
/// <param name="frame">待归还的智能帧</param>
public void Return(SmartFrame frame)
{
_availableFrames.Enqueue(frame);
}
#endregion
#region --- (Resource Disposal) ---
/// <summary>
/// 释放帧池所有资源,销毁所有 Mat 内存
/// </summary>
public void Dispose()
{
// 遍历所有已分配帧,释放 OpenCV Mat 底层内存
foreach (var frame in _allAllocatedFrames)
{
frame.InternalMat.Dispose();
}
_allAllocatedFrames.Clear();
_availableFrames.Clear();
}
#endregion
}