修复在线导致的一个 Bug

This commit is contained in:
2025-12-26 16:58:12 +08:00
parent 93782bcdf1
commit 83ad6221a4
6 changed files with 190 additions and 10 deletions

View File

@@ -195,7 +195,7 @@ public class CameraManager : IDisposable, IAsyncDisposable
Name = cam.Config.Name,
IpAddress = cam.Config.IpAddress,
Status = cam.Status.ToString(),
IsOnline = cam.IsOnline,
IsOnline = cam.IsPhysicalOnline,
Fps = cam.RealFps,
Bitrate = cam.RealBitrate, // [新增] 映射基类属性
TotalFrames = cam.TotalFrames,

View File

@@ -133,7 +133,7 @@ public class CameraCoordinator
#region --- (Reconciliation Logic) ---
/// <summary>
/// 相机状态调和(核心自愈逻辑)
/// 相机状态调和(核心自愈逻辑 - 修复版
/// 功能:校验相机物理连接、流状态,执行启动/停止/复位操作,确保状态一致性
/// </summary>
/// <param name="cam">待调和的相机设备</param>
@@ -148,12 +148,16 @@ public class CameraCoordinator
bool isFlowing = cam.IsOnline && secondsSinceLastFrame < StreamAliveThresholdSeconds;
// 3. 判定物理连接是否正常:流正常则直接判定在线;否则执行 Ping+TCP 探测
// (注意:如果哨兵已经更新了 Ping 状态ProbeHardwareAsync 内部也可以优化为直接读取,
// 但在 Coordinator 里保留主动探测作为双重保险也是合理的)
bool isPhysicalOk = isFlowing ? true : await ProbeHardwareAsync(cam).ConfigureAwait(false);
// 4. 状态调和决策:根据物理状态与设备状态的差异执行对应操作
// 场景 A: 物理在线 + 设备离线 + 用户要求运行 -> 执行启动
if (isPhysicalOk && !cam.IsOnline && cam.IsRunning)
{
// 物理在线 + 设备离线 + 需运行 → 执行启动(加登录锁防止冲突
// 加登录锁防止冲突
bool lockTaken = false;
try
{
@@ -173,14 +177,15 @@ public class CameraCoordinator
}
}
}
// 场景 B: 物理离线 + 设备在线 -> 执行强制停止
else if (!isPhysicalOk && cam.IsOnline)
{
// 物理离线 + 设备在线 → 执行停止
await cam.StopAsync().ConfigureAwait(false);
}
else if (isPhysicalOk && cam.IsOnline && !isFlowing)
// 场景 C: 物理在线 + 设备在线 + 流中断 + 【用户要求运行】 -> 判定为僵死
// 【关键修复】:增加了 && cam.IsRunning 判定,防止待机状态下被误复位
else if (isPhysicalOk && cam.IsOnline && !isFlowing && cam.IsRunning) // [cite: 504]
{
// 物理在线 + 设备在线 + 流中断 → 判定为僵死,执行复位
Console.WriteLine($"[自愈] 设备 {cam.Id} 僵死({secondsSinceLastFrame:F1}秒无帧),复位中...");
await cam.StopAsync().ConfigureAwait(false);
}

View File

@@ -0,0 +1,103 @@
using System.Net.NetworkInformation;
namespace SHH.CameraSdk;
/// <summary>
/// [状态代理] 网络连通性哨兵
/// 特性:
/// 1. 低耦合:不依赖具体驱动,只依赖接口
/// 2. 高性能:使用 Parallel.ForEachAsync 实现受控并行
/// 3. 智能策略播放中不Ping空闲时才Ping
/// </summary>
public class ConnectivitySentinel
{
private readonly CameraManager _manager; // [cite: 329]
private readonly PeriodicTimer _timer;
private readonly CancellationTokenSource _cts = new();
// [关键配置] 最大并发度
// 建议值CPU 核心数 * 4或者固定 16-32
// 50 个摄像头,设为 16意味着分 4 批完成,总耗时极短
private const int MAX_PARALLELISM = 16;
public ConnectivitySentinel(CameraManager manager)
{
_manager = manager;
// 每 3 秒执行一轮全量巡检
_timer = new PeriodicTimer(TimeSpan.FromSeconds(3));
// 启动后台任务(不阻塞主线程)
_ = RunLoopAsync();
}
private async Task RunLoopAsync()
{
try
{
// 等待下一个 3秒 周期
while (await _timer.WaitForNextTickAsync(_cts.Token))
{
// 1. 获取当前所有设备的快照
// CameraManager.GetAllDevices() 返回的是 BaseVideoSource它实现了 IDeviceConnectivity
var devices = _manager.GetAllDevices().Cast<IDeviceConnectivity>();
// 2. [核心回答] 受控并行执行
// .NET 6+ 提供的超级 API专门解决“一下子 50 个”的问题
await Parallel.ForEachAsync(devices, new ParallelOptions
{
MaxDegreeOfParallelism = MAX_PARALLELISM,
CancellationToken = _cts.Token
},
async (device, token) =>
{
// 对每个设备执行独立检查
await CheckSingleDeviceAsync(device);
});
}
}
catch (OperationCanceledException) { /* 正常停止 */ }
}
private async Task CheckSingleDeviceAsync(IDeviceConnectivity device)
{
bool isAlive = false;
// [智能策略]:如果设备正在取流,直接检查帧心跳(省流模式)
if (device.Status == VideoSourceStatus.Playing || device.Status == VideoSourceStatus.Streaming)
{
long now = Environment.TickCount64;
// 5秒内有帧就算在线
isAlive = (now - device.LastFrameTick) < 5000;
}
else
{
// [主动探测]:空闲或离线时,发射 ICMP Ping
isAlive = await PingAsync(device.IpAddress);
}
// [状态注入]:将探测结果“注入”回设备
device.SetNetworkStatus(isAlive);
}
// 纯粹的 Ping 逻辑
private async Task<bool> PingAsync(string ip)
{
try
{
using var ping = new Ping();
// 超时设为 800ms快速失败避免拖慢整体批次
var reply = await ping.SendPingAsync(ip, 800);
return reply.Status == IPStatus.Success;
}
catch
{
return false;
}
}
public void Stop()
{
_cts.Cancel();
_timer.Dispose();
}
}