diff --git a/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs b/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs index 7a40ecd..6c6b01a 100644 --- a/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs +++ b/MonitorModule/ViewModels/Dialogs/ValueLimitViewModel.cs @@ -117,13 +117,23 @@ namespace MonitorModule.ViewModels.Dialogs EnsureValueLimit(fingerprint, displayName, displayName, methodName); } - // 3. 发现 CAN 信号并加入 ValueLimitList / DeviceSingleList + // 3. 发现 CAN 信号并加入 ValueLimitList,同时收集有效信号名 + var validCanSignalNames = new HashSet(); foreach (var (displayName, fingerprint, methodName) in DiscoverCanSignals()) { - //DeviceSingleList.Add(displayName); _canSignalMap[displayName] = (fingerprint, methodName); EnsureValueLimit(fingerprint, displayName, displayName, methodName); + validCanSignalNames.Add(displayName); } + + // 4. 清理已不存在于 DBC 中的 CAN 信号(Fingerprint 以 "CAN" 开头且不在有效集合中) + var staleCanSignals = ValueLimitList + .Where(x => !string.IsNullOrEmpty(x.Fingerprint) && + x.Fingerprint.StartsWith("CAN") && + !validCanSignalNames.Contains(x.SignalName)) + .ToList(); + foreach (var item in staleCanSignals) + ValueLimitList.Remove(item); } /// diff --git a/UIShare/UIViewModel/MonitorChannel.cs b/UIShare/UIViewModel/MonitorChannel.cs index 8912480..02bcdc0 100644 --- a/UIShare/UIViewModel/MonitorChannel.cs +++ b/UIShare/UIViewModel/MonitorChannel.cs @@ -83,7 +83,7 @@ namespace UIShare.UIViewModel if (_series != null) { _series.Points.Add(new DataPoint(time, displayValue)); - while (_series.Points.Count > 200) + while (_series.Points.Count > 10000) { _series.Points.RemoveAt(0); } diff --git a/ZLGUSBCANFD/USBCANFD.cs b/ZLGUSBCANFD/USBCANFD.cs index 4713ea7..6208642 100644 --- a/ZLGUSBCANFD/USBCANFD.cs +++ b/ZLGUSBCANFD/USBCANFD.cs @@ -25,10 +25,11 @@ namespace ZLGUSBCANFD // 异步高性能接收线程控制 private volatile bool _isRunning = false; + private volatile bool _isClosing = false; // 关闭流程标志:通知循环发送任务尽快退出 private readonly List _receiveThreads = new List(); - // DBC 循环发送任务管理:Key = (通道号, 帧ID) - private readonly ConcurrentDictionary<(uint 通道号, uint 帧ID), CancellationTokenSource> _cyclicSenders = new ConcurrentDictionary<(uint, uint), CancellationTokenSource>(); + // DBC 循环发送任务管理:Key = (通道号, 帧ID),Value = (CTS, Task) + private readonly ConcurrentDictionary<(uint 通道号, uint 帧ID), (CancellationTokenSource Cts, Task SendTask)> _cyclicSenders = new ConcurrentDictionary<(uint, uint), (CancellationTokenSource, Task)>(); // 信号值持久化覆盖表:Key = (通道号, 帧ID),Value = 信号名 → 物理值 // 每次 设置报文 会累积写入此表,发送时以 DBC 初始值为底再叠加此表覆盖值 @@ -98,15 +99,17 @@ namespace ZLGUSBCANFD /// public virtual bool 初始化并启动通道(uint 通道号) { + _isClosing = false; // 重置关闭标志,允许循环发送 if (_deviceHandle == IntPtr.Zero) throw new InvalidOperationException("请先调用 '打开设备()' 才能初始化通道。"); if (通道号 >= _maxChannels) return false; lock (_channelLocks[通道号]) { - // 如果已经启动过,先复位 + // 如果已经启动过,直接退出 if (_channelHandles[通道号] != IntPtr.Zero) { - ZLGCAN.ZCAN_ResetCAN(_channelHandles[通道号]); + return true; + //ZLGCAN.ZCAN_ResetCAN(_channelHandles[通道号]); } // 1. 设置该通道专属的仲裁域与数据域波特率 @@ -150,19 +153,44 @@ namespace ZLGUSBCANFD public virtual void 关闭CAN卡设备() { - _isRunning = false; - 停止所有循环发送(); - Thread.Sleep(50); // 确保轮询线程安全退出 + _isClosing = true; // 通知循环发送任务尽快退出 + _isRunning = false; // 通知接收轮询线程退出 + 停止所有循环发送(); // 内部会等待所有循环发送 Task 退出 + + // 等待所有接收轮询线程真正退出(每个最多 1 秒) + foreach (var thread in _receiveThreads) + { + if (thread.IsAlive) + thread.Join(1000); + } _receiveThreads.Clear(); - //动态复位所有通道并释放DBC + // 动态复位所有通道并释放 DBC(使用 TryEnter 防止死锁) for (uint i = 0; i < _maxChannels; i++) { - lock (_channelLocks[i]) + if (Monitor.TryEnter(_channelLocks[i], TimeSpan.FromSeconds(2))) { + try + { + if (_channelHandles[i] != IntPtr.Zero) + { + ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]); + _channelHandles[i] = IntPtr.Zero; + } + 释放通道DBC(i); + } + finally + { + Monitor.Exit(_channelLocks[i]); + } + } + else + { + // 锁超时:循环发送 Task 可能仍阻塞在原生 API 调用中,强制清理 + LoggerHelper.Info($"[ZLGCANFD] 关闭通道 {i} 时获取锁超时,强制清理"); if (_channelHandles[i] != IntPtr.Zero) { - ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]); + try { ZLGCAN.ZCAN_ResetCAN(_channelHandles[i]); } catch { } _channelHandles[i] = IntPtr.Zero; } 释放通道DBC(i); @@ -175,6 +203,8 @@ namespace ZLGUSBCANFD ZLGCAN.ZCAN_CloseDevice(_deviceHandle); _deviceHandle = IntPtr.Zero; } + + _isClosing = false; } #endregion @@ -354,17 +384,16 @@ namespace ZLGUSBCANFD 停止循环发送(通道号, 帧ID); var cts = new CancellationTokenSource(); - _cyclicSenders[(通道号, 帧ID)] = cts; - - Task.Run(async () => + var sendTask = Task.Run(async () => { while (!cts.Token.IsCancellationRequested) { + if (_isClosing) break; // 关闭流程中立即退出 try { lock (_channelLocks[通道号]) { - if (_channelHandles[通道号] == IntPtr.Zero) break; + if (_channelHandles[通道号] == IntPtr.Zero || _isClosing) break; 发送DBC定义报文单次(通道号, 帧ID, 信号物理值字典, 是否使用CANFD); } @@ -382,13 +411,15 @@ namespace ZLGUSBCANFD } // 清理:仅当字典中存的仍然是本任务创建的 CTS 时才移除,避免误删新任务的 CTS - if (_cyclicSenders.TryGetValue((通道号, 帧ID), out var current) && ReferenceEquals(current, cts)) + if (_cyclicSenders.TryGetValue((通道号, 帧ID), out var current) && ReferenceEquals(current.Cts, cts)) { _cyclicSenders.TryRemove((通道号, 帧ID), out _); } cts.Dispose(); }, cts.Token); + _cyclicSenders[(通道号, 帧ID)] = (cts, sendTask); + return true; } @@ -397,9 +428,9 @@ namespace ZLGUSBCANFD /// public virtual void 停止循环发送(uint 通道号, uint 帧ID) { - if (_cyclicSenders.TryRemove((通道号, 帧ID), out var cts)) + if (_cyclicSenders.TryRemove((通道号, 帧ID), out var entry)) { - cts.Cancel(); + entry.Cts.Cancel(); // 不在这里 Dispose,由 Task 的清理代码负责释放,避免 Task 仍在使用已释放的 Token } } @@ -409,11 +440,30 @@ namespace ZLGUSBCANFD /// public virtual void 停止所有循环发送() { - foreach (var kvp in _cyclicSenders) + var entries = _cyclicSenders.ToArray(); + + // 1. 取消所有 CTS(不 Dispose,由 Task 清理代码负责) + foreach (var kvp in entries) { - kvp.Value.Cancel(); - kvp.Value.Dispose(); + kvp.Value.Cts.Cancel(); } + + // 2. 等待所有循环发送 Task 退出(最多 3 秒) + var tasks = new List(); + foreach (var kvp in entries) + { + if (!kvp.Value.SendTask.IsCompleted) + tasks.Add(kvp.Value.SendTask); + } + if (tasks.Count > 0) + { + try + { + Task.WaitAll(tasks.ToArray(), TimeSpan.FromSeconds(3)); + } + catch { /* 忽略等待异常 */ } + } + _cyclicSenders.Clear(); }