using Core.WcfProtocol; using System.Collections.Concurrent; namespace SHH.MjpegPlayer; /// /// 服务器会话集合 (线程安全重构版) /// public class MjpegSessions { // 核心改变:使用字典建立索引,Key = DeviceId#TypeCode // 这样可以将查找特定摄像头的复杂度从 O(N) 降低到 O(1) private readonly ConcurrentDictionary> _sessionMap = new ConcurrentDictionary>(); /// /// 构造函数 /// public MjpegSessions() { PrismMsg.Subscribe(ProcUploadImageRequest); } /// /// 优化后的图片分发逻辑 (解决 O(N) 和 线程安全问题) /// /// public void ProcUploadImageRequest(UploadImageRequest req) { try { string key = $"{req.Id}#{req.Type}"; // 1. 更新通道信息 (ImageChannels 现已是线程安全的) var chn = MjpegStatics.ImageChannels.Do(req, key); // 2. O(1) 快速查找关注该流的会话列表 if (_sessionMap.TryGetValue(key, out var targetSessions)) { // 必须加锁,防止遍历时 List 被其他线程(如 AddSession/RemoveSession) 修改 lock (targetSessions) { // 倒序遍历,方便在需要时移除失效会话 for (var i = targetSessions.Count - 1; i >= 0; i--) { var session = targetSessions[i]; session.DoImageProc(req); } } if (chn != null) chn.IsPlaying = targetSessions.Count > 0; } else { if (chn != null) chn.IsPlaying = false; } } catch (Exception ex) { //Logs.LogWarning(ex.Message, ex.StackTrace); } } /// /// 添加会话 /// /// public void AddSession(MjpegSession session) { if (session?.Info?.Key == null) return; // 使用 GetOrAdd 确保线程安全地获取或创建 List var list = _sessionMap.GetOrAdd(session.Info.Key, _ => new List()); lock (list) { list.Add(session); } } /// /// 移除会话 /// /// public void RemoveSession(MjpegSession session) { if (session?.Info?.Key == null) return; if (_sessionMap.TryGetValue(session.Info.Key, out var list)) { lock (list) { list.Remove(session); } } } /// /// 获取当前所有会话信息的快照 (用于 HTTP API 统计与展示) /// [新增] 此方法替代旧版直接访问 Sessions 列表,防止 HTTP 线程与 MJPEG 线程发生冲突 /// public List GetAllSessionInfos() { var result = new List(); // 遍历字典,线程安全地收集所有 Info foreach (var kvp in _sessionMap) { // 对内部 List 加锁,确保复制过程不被打断 lock (kvp.Value) { result.AddRange(kvp.Value.Select(s => s.Info)); } } return result; } }