diff --git a/CapMachine.Wpf/CanDrive/CanDbcModel.cs b/CapMachine.Wpf/CanDrive/CanDbcModel.cs
index fa7f0ea..97d6fb8 100644
--- a/CapMachine.Wpf/CanDrive/CanDbcModel.cs
+++ b/CapMachine.Wpf/CanDrive/CanDbcModel.cs
@@ -1,4 +1,4 @@
-using Prism.Mvvm;
+using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -47,8 +47,18 @@ namespace CapMachine.Wpf.CanDrive
private string? _SignalRtValue = "--";
///
- /// 信号实时值
+ /// 信号实时值(供 UI 绑定显示)。
///
+ ///
+ /// - 仅当文本发生变化时才触发通知,避免无谓 UI 刷新。
+ /// - 若期望进一步降低分配开销,建议在调用方(接收循环)先做“数值层去重”,
+ /// 即:仅在数值变化(可设置容差)时才调用 ToString() 格式化并赋值到本属性。
+ /// 这样可以显著减少字符串分配与 GC 压力。
+ ///
+ ///
+ /// // 示例:数值层去重后再格式化(接收循环中使用)
+ /// // if (Math.Abs(newVal - lastVal) > 1e-3) { model.SignalRtValue = newVal.ToString("F3"); lastVal = newVal; }
+ ///
public string? SignalRtValue
{
get { return _SignalRtValue; }
@@ -62,21 +72,56 @@ namespace CapMachine.Wpf.CanDrive
}
}
- private StringBuilder _SignalRtValueSb = new StringBuilder(10);
+ private StringBuilder _SignalRtValueSb = new StringBuilder(16);
///
- /// 信号实时值 StringBuilder
+ /// 信号实时值的可变文本缓冲接口。
+ /// 设计目的:
+ /// - 避免外部传入的 StringBuilder 被直接保存(引用别名问题),统一复制文本到内部缓冲 _SignalRtValueSb;
+ /// - 仅当文本内容变化时才更新 SignalRtValue 并 RaisePropertyChanged,减少 UI 抖动与无谓刷新;
+ /// - 对 value == null 做防御:清空内部缓冲并置空显示文本(如需显示 “--” 可按需替换)。
+ /// 使用方式:
+ /// - 接收环路可重复复用同一个外部 StringBuilder 作为临时缓存,调用本属性进行更新不会产生实例共享风险。
///
+ ///
+ /// 注意事项:
+ /// 1) 本属性会复制文本,不会保存外部 StringBuilder 引用;这可避免多个模型共享同一实例导致的数据串扰和并发问题。
+ /// 2) 若你的 UI 需要统一的“空值占位符”,可将 setter 中的空字符串替换为 "--",与字段初始值保持一致。
+ /// 3) 线程模型:建议仍在 UI 线程更新以避免跨线程通知问题;如需后台线程更新,请确保有合适的调度(Dispatcher/同步上下文)。
+ /// 4) 性能建议:结合 SignalRtValue 的备注,在进入本 setter 前尽量做数值层去重,进一步减少字符串分配。
+ ///
+ ///
+ /// // 示例:接收循环中复用临时缓存
+ /// // var tmp = new StringBuilder(32);
+ /// // ... 填充 tmp ...
+ /// // model.SignalRtValueSb = tmp; // 本属性会复制 tmp 的内容,安全且不会产生实例共享
+ ///
public StringBuilder SignalRtValueSb
{
get { return _SignalRtValueSb; }
set
{
- //if (_SignalRtValueSb != value)
- //{
- SignalRtValue = value.ToString();
- _SignalRtValueSb = value;
- //}
+ // 防御:若外部传入 null,清空内部状态并复位显示文本
+ // 注意:此处将显示文本设为空字符串 "",如果希望与初始占位符 "--" 一致,可改为 SignalRtValue = "--"。
+ if (value == null)
+ {
+ if (_SignalRtValue != string.Empty)
+ {
+ _SignalRtValueSb.Clear();
+ SignalRtValue = string.Empty;
+ }
+ return;
+ }
+ // 复制策略:不保存外部 StringBuilder 的引用,改为复制其当前文本内容
+ // 这样可避免多个模型共享同一 StringBuilder 实例导致的数据串扰与线程安全问题
+ var str = value.ToString();
+ // 仅当文本内容确实发生变化时,才更新内部缓冲与绑定属性,减少无谓的 UI 刷新与字符串分配
+ if (!string.Equals(_SignalRtValue, str, StringComparison.Ordinal))
+ {
+ _SignalRtValueSb.Clear();
+ _SignalRtValueSb.Append(str);
+ SignalRtValue = str;
+ }
}
}
diff --git a/CapMachine.Wpf/CanDrive/ToomossCan.cs b/CapMachine.Wpf/CanDrive/ToomossCan.cs
index 615d953..95dd74e 100644
--- a/CapMachine.Wpf/CanDrive/ToomossCan.cs
+++ b/CapMachine.Wpf/CanDrive/ToomossCan.cs
@@ -1,4 +1,4 @@
-using CapMachine.Core;
+using CapMachine.Core;
using CapMachine.Model.CANLIN;
using CapMachine.Wpf.Dtos;
using CapMachine.Wpf.Models.Tag;
@@ -12,11 +12,13 @@ using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
+using System.Collections.Concurrent;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
+using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Interop;
@@ -500,6 +502,32 @@ namespace CapMachine.Wpf.CanDrive
StringBuilder ValueSb = new StringBuilder(16);
double[] ValueDouble = new double[5];
+ // 接收缓冲池(重用,避免每轮分配)
+ private IntPtr RecvMsgBufferPtr = IntPtr.Zero;
+ private int RecvMsgBufferCapacity = 1024;
+ private readonly int CanMsgSize = Marshal.SizeOf(typeof(USB2CAN.CAN_MSG));
+
+ // 名称 StringBuilder 缓存(DBC 调用复用,避免频繁分配)
+ private readonly Dictionary MsgNameSBCache = new Dictionary(StringComparer.Ordinal);
+ private readonly Dictionary SigNameSBCache = new Dictionary(StringComparer.Ordinal);
+
+ // 控制台调试输出开关(默认关闭,防止日志风暴)
+ public bool EnableConsoleDebugLog { get; set; } = false;
+
+ // 保护接收缓冲的并发锁(接收读与关闭释放之间的互斥)
+ private readonly object RecvBufferSync = new object();
+
+ private StringBuilder GetCachedSB(Dictionary cache, string key)
+ {
+ key ??= string.Empty;
+ if (cache.TryGetValue(key, out var sb)) return sb;
+ var nsb = new StringBuilder(key);
+ cache[key] = nsb;
+ return nsb;
+ }
+ private StringBuilder GetMsgSB(string key) => GetCachedSB(MsgNameSBCache, key);
+ private StringBuilder GetSigSB(string key) => GetCachedSB(SigNameSBCache, key);
+
private bool _IsSendOk;
///
/// 发送报文是否OK
@@ -1258,101 +1286,117 @@ namespace CapMachine.Wpf.CanDrive
///
public void StartCycleReviceCanMsg()
{
+ // 防止重复启动,若已有任务在运行则直接返回
+ if (CycleReviceTask != null && !CycleReviceTask.IsCompleted)
+ {
+ return;
+ }
+
CycleReviceTask = Task.Run(async () =>
{
- while (IsCycleRevice)
+ try
{
- await Task.Delay(ReviceCycle);
- try
+ while (IsCycleRevice)
{
- //另外一个CAN通道读取数据
- USB2CAN.CAN_MSG[] CanMsgBuffer = new USB2CAN.CAN_MSG[1024];
- //申请数据缓冲区
- IntPtr msgPtRead = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)) * CanMsgBuffer.Length);
- int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, msgPtRead, CanMsgBuffer.Length);
- //int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, 1, msgPtRead, CanMsgBuffer.Length);//测试用,CAN卡 CAN1和CAN2 短接时测试用
- if (CanNum > 0)
+ await Task.Delay(ReviceCycle);
+ try
{
- IsReviceOk = true;
- Console.WriteLine("Read CanMsgNum = {0}", CanNum);
- for (int i = 0; i < CanNum; i++)
+ // 另一个CAN通道读取数据(与 CloseDevice 释放互斥,保护指针安全)
+ IntPtr msgPtRead;
+ int CanNum;
+ lock (RecvBufferSync)
+ {
+ if (RecvMsgBufferPtr == IntPtr.Zero)
{
- //CanMsgBuffer[i] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure((IntPtr)((UInt32)msgPtRead + i * Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))), typeof(USB2CAN.CAN_MSG)); //有溢出报错
- CanMsgBuffer[i] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure((IntPtr)(msgPtRead + i * Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))), typeof(USB2CAN.CAN_MSG));
-
- Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, CanMsgBuffer[i].ID.ToString("X8"));
- Console.WriteLine("CanMsg[{0}].TimeStamp = {1}", i, CanMsgBuffer[i].TimeStamp);
- Console.Write("CanMsg[{0}].Data = ", i);
- for (int j = 0; j < CanMsgBuffer[i].DataLen; j++)
- {
- Console.Write("{0} ", CanMsgBuffer[i].Data[j].ToString("X2"));
- }
- Console.WriteLine("");
-
- //报文给高速记录的服务
- HighSpeedDataService.AppendOrUpdateMsg(new Models.HighSpeed.CommMsg()
- {
- Category = "CAN",
- MsgInfo = "0x" + CanMsgBuffer[i].ID.ToString("X8"),
- MsgData = BitConverter.ToString(CanMsgBuffer[i].Data),
- Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
- });
+ RecvMsgBufferPtr = Marshal.AllocHGlobal(CanMsgSize * RecvMsgBufferCapacity);
}
- }
- else if (CanNum == 0)
- {
- IsReviceOk = false;
- Console.WriteLine("No CAN data!");
- }
- else
- {
- IsReviceOk = false;
- Console.WriteLine("Get CAN data error!");
- }
- //Console.WriteLine("");
+ msgPtRead = RecvMsgBufferPtr;
+ CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, msgPtRead, RecvMsgBufferCapacity);
+ //int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, 1, msgPtRead, RecvMsgBufferCapacity);//测试用,CAN卡 CAN1和CAN2 短接时测试用
+ if (CanNum > 0)
+ {
+ IsReviceOk = true;
+ if (EnableConsoleDebugLog) Console.WriteLine("Read CanMsgNum = {0}", CanNum);
+ for (int i = 0; i < CanNum; i++)
+ {
+ var msgPtr = (IntPtr)(msgPtRead + i * CanMsgSize);
+ var msg = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(msgPtr, typeof(USB2CAN.CAN_MSG));
- //将CAN消息数据填充到信号里面,用DBC解析数据
- CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, msgPtRead, CanNum);
+ if (EnableConsoleDebugLog)
+ {
+ Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, msg.ID.ToString("X8"));
+ Console.WriteLine("CanMsg[{0}].TimeStamp = {1}", i, msg.TimeStamp);
+ Console.Write("CanMsg[{0}].Data = ", i);
+ for (int j = 0; j < msg.DataLen; j++)
+ {
+ Console.Write("{0} ", msg.Data[j].ToString("X2"));
+ }
+ Console.WriteLine("");
+ }
+
+ // 报文给高速记录的服务
+ HighSpeedDataService.AppendOrUpdateMsg(new Models.HighSpeed.CommMsg()
+ {
+ Category = "CAN",
+ MsgInfo = "0x" + msg.ID.ToString("X8"),
+ MsgData = BitConverter.ToString(msg.Data),
+ Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")
+ });
+ }
+ }
+ else if (CanNum == 0)
+ {
+ IsReviceOk = false;
+ if (EnableConsoleDebugLog) Console.WriteLine("No CAN data!");
+ }
+ else
+ {
+ IsReviceOk = false;
+ if (EnableConsoleDebugLog) Console.WriteLine("Get CAN data error!");
+ }
+ // 将CAN消息数据填充到信号里面,用DBC解析数据(仍在锁内,避免指针被并发释放)
+ CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, msgPtRead, CanNum);
+ }
//循环获取消息的数据
foreach (var item in ListCanDbcModel)
{
- //有配置的名称的,认为是有用的,则需要读取数据
- //if (!string.IsNullOrEmpty(item.Name))
- //{
- //CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ValueSb);
- //double[] ValueDouble;
- CAN_DBCParser.DBC_GetSignalValue(DBCHandle, new StringBuilder(item.MsgName), new StringBuilder(item.SignalName), ValueDouble);
- //item.SignalRtValueSb = ValueSb;
+ // 复用 StringBuilder 缓存,避免频繁分配
+ var msgNameSB = GetMsgSB(item.MsgName);
+ var sigNameSB = GetSigSB(item.SignalName);
+ CAN_DBCParser.DBC_GetSignalValue(DBCHandle, msgNameSB, sigNameSB, ValueDouble);
item.SignalRtValue = ValueDouble[0].ToString();
//Console.Write(ValueSb.ToString());
- //}
}
+ // 缓冲区在 CloseDevice 或任务退出的 finally 中统一释放,避免频繁申请/释放
- //释放数据缓冲区,必须释放,否则程序运行一段时间后会报内存不足
- Marshal.FreeHGlobal(msgPtRead);
- Thread.Sleep(10);
- ////获取信号值并打印出来
- //StringBuilder ValueStr = new StringBuilder(32);
- //CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_moto_speed"), new StringBuilder("moto_speed"), ValueStr);
- //Console.WriteLine("moto_speed = {0}", ValueStr);
- //CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_oil_pressure"), new StringBuilder("oil_pressure"), ValueStr);
- //Console.WriteLine("oil_pressure = {0}", ValueStr);
- //CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_speed_can"), new StringBuilder("speed_can"), ValueStr);
- //Console.WriteLine("speed_can = {0}", ValueStr);
- }
- catch (Exception ex)
- {
- IsReviceOk = false;
- LoggerService.Info("接收出现异常");
- }
+ }
+ catch (Exception ex)
+ {
+ IsReviceOk = false;
+ LoggerService.Info("接收出现异常");
+ }
//finally
//{
// IsReviceOk = false;
//}
+ }
+ }
+ finally
+ {
+ IsReviceOk = false;
+ // 接收任务退出时释放接收缓冲,避免仅停止接收时的缓冲常驻
+ lock (RecvBufferSync)
+ {
+ if (RecvMsgBufferPtr != IntPtr.Zero)
+ {
+ try { Marshal.FreeHGlobal(RecvMsgBufferPtr); }
+ catch { }
+ finally { RecvMsgBufferPtr = IntPtr.Zero; }
+ }
+ }
}
- IsReviceOk = false;
});
}
@@ -1364,43 +1408,55 @@ namespace CapMachine.Wpf.CanDrive
//另外一个CAN通道读取数据
USB2CAN.CAN_MSG[] CanMsgBuffer = new USB2CAN.CAN_MSG[10];
msgPt = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)) * CanMsgBuffer.Length);
- int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, msgPt, CanMsgBuffer.Length);
- if (CanNum > 0)
+ try
{
- Console.WriteLine("Read CanMsgNum = {0}", CanNum);
- for (int i = 0; i < CanNum; i++)
+ int CanNum = USB2CAN.CAN_GetMsgWithSize(DevHandle, ReadCANIndex, msgPt, CanMsgBuffer.Length);
+ if (CanNum > 0)
{
- CanMsgBuffer[i] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure((IntPtr)((UInt32)msgPt + i * Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))), typeof(USB2CAN.CAN_MSG));
- Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, CanMsgBuffer[i].ID.ToString("X8"));
- //Console.WriteLine("CanMsg[{0}].TimeStamp = {1}",i,CanMsgBuffer[i].TimeStamp);
- Console.Write("CanMsg[{0}].Data = ", i);
- for (int j = 0; j < CanMsgBuffer[i].DataLen; j++)
+ Console.WriteLine("Read CanMsgNum = {0}", CanNum);
+ for (int i = 0; i < CanNum; i++)
{
- Console.Write("{0} ", CanMsgBuffer[i].Data[j].ToString("X2"));
+ CanMsgBuffer[i] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure((IntPtr)((UInt32)msgPt + i * Marshal.SizeOf(typeof(USB2CAN.CAN_MSG))), typeof(USB2CAN.CAN_MSG));
+ Console.WriteLine("CanMsg[{0}].ID = 0x{1}", i, CanMsgBuffer[i].ID.ToString("X8"));
+ //Console.WriteLine("CanMsg[{0}].TimeStamp = {1}",i,CanMsgBuffer[i].TimeStamp);
+ Console.Write("CanMsg[{0}].Data = ", i);
+ for (int j = 0; j < CanMsgBuffer[i].DataLen; j++)
+ {
+ Console.Write("{0} ", CanMsgBuffer[i].Data[j].ToString("X2"));
+ }
+ Console.WriteLine("");
}
- Console.WriteLine("");
+ }
+ else if (CanNum == 0)
+ {
+ Console.WriteLine("No CAN data!");
+ }
+ else
+ {
+ Console.WriteLine("Get CAN data error!");
+ }
+ Console.WriteLine("");
+
+ //将CAN消息数据填充到信号里面
+ CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, msgPt, CanNum);
+ //获取信号值并打印出来
+ StringBuilder ValueStr = new StringBuilder(32);
+ CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_moto_speed"), new StringBuilder("moto_speed"), ValueStr);
+ Console.WriteLine("moto_speed = {0}", ValueStr);
+ CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_oil_pressure"), new StringBuilder("oil_pressure"), ValueStr);
+ Console.WriteLine("oil_pressure = {0}", ValueStr);
+ CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_speed_can"), new StringBuilder("speed_can"), ValueStr);
+ Console.WriteLine("speed_can = {0}", ValueStr);
+ }
+ finally
+ {
+ if (msgPt != IntPtr.Zero)
+ {
+ try { Marshal.FreeHGlobal(msgPt); }
+ catch { }
+ finally { msgPt = IntPtr.Zero; }
}
}
- else if (CanNum == 0)
- {
- Console.WriteLine("No CAN data!");
- }
- else
- {
- Console.WriteLine("Get CAN data error!");
- }
- Console.WriteLine("");
-
- //将CAN消息数据填充到信号里面
- CAN_DBCParser.DBC_SyncCANMsgToValue(DBCHandle, msgPt, CanNum);
- //获取信号值并打印出来
- StringBuilder ValueStr = new StringBuilder(32);
- CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_moto_speed"), new StringBuilder("moto_speed"), ValueStr);
- Console.WriteLine("moto_speed = {0}", ValueStr);
- CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_oil_pressure"), new StringBuilder("oil_pressure"), ValueStr);
- Console.WriteLine("oil_pressure = {0}", ValueStr);
- CAN_DBCParser.DBC_GetSignalValueStr(DBCHandle, new StringBuilder("msg_speed_can"), new StringBuilder("speed_can"), ValueStr);
- Console.WriteLine("speed_can = {0}", ValueStr);
}
@@ -1419,6 +1475,27 @@ namespace CapMachine.Wpf.CanDrive
{
StopSchedule();
}
+
+ // 等待接收任务结束后释放非托管缓冲区,避免并发释放
+ try
+ {
+ var task = CycleReviceTask;
+ if (task != null && !task.IsCompleted)
+ {
+ task.Wait(TimeSpan.FromMilliseconds(ReviceCycle + 500));
+ }
+ }
+ catch { }
+ // 在锁内安全释放,避免与接收线程并发访问同一指针
+ lock (RecvBufferSync)
+ {
+ if (RecvMsgBufferPtr != IntPtr.Zero)
+ {
+ try { Marshal.FreeHGlobal(RecvMsgBufferPtr); }
+ catch { }
+ finally { RecvMsgBufferPtr = IntPtr.Zero; }
+ }
+ }
}
}
diff --git a/CapMachine.Wpf/CapMachine.Wpf.csproj b/CapMachine.Wpf/CapMachine.Wpf.csproj
index 38d28f8..1fcae89 100644
--- a/CapMachine.Wpf/CapMachine.Wpf.csproj
+++ b/CapMachine.Wpf/CapMachine.Wpf.csproj
@@ -174,6 +174,7 @@
+
PreserveNewest