Files
Ayay/SHH.CameraSdk/Core/Services/ConnectivitySentinel.cs

103 lines
3.3 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 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();
}
}