定时器发送 初版
This commit is contained in:
@@ -615,6 +615,234 @@ namespace CapMachine.Wpf.CanDrive
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#region 定时器发送报文数据
|
||||||
|
|
||||||
|
// 定时器发送:采用单次触发+校准的方式避免周期漂移,减少长时间运行误差
|
||||||
|
// 说明:不修改 StartPrecisionCycleSendMsg 的任何内容;此处仅实现定时器版本
|
||||||
|
|
||||||
|
// 定时器与资源
|
||||||
|
private System.Threading.Timer _sendTimer;
|
||||||
|
private readonly object _sendTimerSync = new object();
|
||||||
|
private volatile int _sendTimerRunningFlag = 0; // 防止回调重入
|
||||||
|
private int _sendTimerPeriodMs = 100; // 默认100ms
|
||||||
|
private readonly Stopwatch _sendTimerWatch = new Stopwatch();
|
||||||
|
private long _sendTimerNextTicks;
|
||||||
|
|
||||||
|
// 发送缓冲重用,减少分配
|
||||||
|
private USB2CAN.CAN_MSG[] _timerCanMsgBuffer;
|
||||||
|
private IntPtr _timerMsgPtr = IntPtr.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动基于定时器的周期发送(毫秒)。支持典型的50/100/200ms等周期。
|
||||||
|
/// 使用单次触发+绝对时间校准的算法,尽量减少漂移;并使用重用缓冲降低内存分配。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="periodMs">发送周期(毫秒)</param>
|
||||||
|
public void StartTimerCycleSendMsg(int periodMs)
|
||||||
|
{
|
||||||
|
if (periodMs <= 0) periodMs = 1;
|
||||||
|
|
||||||
|
lock (_sendTimerSync)
|
||||||
|
{
|
||||||
|
// 先停止旧的
|
||||||
|
StopTimerCycleSendMsg_NoLock();
|
||||||
|
|
||||||
|
_sendTimerPeriodMs = periodMs;
|
||||||
|
IsCycleSend = true;
|
||||||
|
|
||||||
|
if (_timerMsgPtr == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
_timerMsgPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(USB2CAN.CAN_MSG)));
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendTimerWatch.Restart();
|
||||||
|
_sendTimerNextTicks = _sendTimerWatch.ElapsedTicks + (long)(_sendTimerPeriodMs * TicksPerMs);
|
||||||
|
|
||||||
|
// 使用单次触发模式,每次回调后手动调度下一次,便于按绝对时间校准
|
||||||
|
_sendTimer = new System.Threading.Timer(SendTimerCallback, null, 0, System.Threading.Timeout.Infinite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动基于定时器的周期发送(使用 SendCycle 属性作为周期参数)
|
||||||
|
/// </summary>
|
||||||
|
public void StartTimerCycleSendMsg()
|
||||||
|
{
|
||||||
|
StartTimerCycleSendMsg((int)SendCycle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 便捷方法:启动50ms周期发送
|
||||||
|
/// </summary>
|
||||||
|
public void StartTimerCycleSendMsg50ms() => StartTimerCycleSendMsg(50);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 便捷方法:启动100ms周期发送
|
||||||
|
/// </summary>
|
||||||
|
public void StartTimerCycleSendMsg100ms() => StartTimerCycleSendMsg(100);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 便捷方法:启动200ms周期发送
|
||||||
|
/// </summary>
|
||||||
|
public void StartTimerCycleSendMsg200ms() => StartTimerCycleSendMsg(200);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 停止定时器周期发送并释放资源
|
||||||
|
/// </summary>
|
||||||
|
public void StopTimerCycleSendMsg()
|
||||||
|
{
|
||||||
|
lock (_sendTimerSync)
|
||||||
|
{
|
||||||
|
IsCycleSend = false;
|
||||||
|
StopTimerCycleSendMsg_NoLock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopTimerCycleSendMsg_NoLock()
|
||||||
|
{
|
||||||
|
var t = _sendTimer;
|
||||||
|
_sendTimer = null;
|
||||||
|
if (t != null)
|
||||||
|
{
|
||||||
|
try { t.Dispose(); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待在途回调结束,避免释放资源时发生竞争
|
||||||
|
System.Threading.SpinWait.SpinUntil(() => System.Threading.Interlocked.CompareExchange(ref _sendTimerRunningFlag, 0, 0) == 0, 200);
|
||||||
|
|
||||||
|
_sendTimerWatch.Stop();
|
||||||
|
|
||||||
|
if (_timerMsgPtr != IntPtr.Zero)
|
||||||
|
{
|
||||||
|
try { Marshal.FreeHGlobal(_timerMsgPtr); } catch { } finally { _timerMsgPtr = IntPtr.Zero; }
|
||||||
|
}
|
||||||
|
_timerCanMsgBuffer = null; // 让GC回收
|
||||||
|
IsSendOk = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定时器回调(单次触发);回调结束时会按绝对时间校准下一次触发
|
||||||
|
private void SendTimerCallback(object state)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!IsCycleSend) return;
|
||||||
|
|
||||||
|
// 防止回调重入(当某次发送耗时超过周期时避免并发)
|
||||||
|
if (System.Threading.Interlocked.Exchange(ref _sendTimerRunningFlag, 1) == 1)
|
||||||
|
{
|
||||||
|
ScheduleNextTick();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var localCmd = CmdData; // 快照引用,避免枚举时被替换
|
||||||
|
if (localCmd == null || localCmd.Count == 0)
|
||||||
|
{
|
||||||
|
IsSendOk = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 依据消息名进行分组(与 StartPrecisionCycleSendMsg 的发送逻辑一致)
|
||||||
|
IEnumerable<IGrouping<string, CanCmdData>> groupMsg;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
groupMsg = localCmd.GroupBy(x => x.MsgName).ToList(); // ToList 避免重复枚举
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 罕见并发异常,跳过本次
|
||||||
|
IsSendOk = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int msgCount = groupMsg.Count();
|
||||||
|
if (msgCount <= 0)
|
||||||
|
{
|
||||||
|
IsSendOk = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保发送缓冲容量,尽量复用,减少频繁分配
|
||||||
|
if (_timerCanMsgBuffer == null || _timerCanMsgBuffer.Length != msgCount)
|
||||||
|
{
|
||||||
|
_timerCanMsgBuffer = new USB2CAN.CAN_MSG[msgCount];
|
||||||
|
for (int i = 0; i < msgCount; i++)
|
||||||
|
{
|
||||||
|
_timerCanMsgBuffer[i] = new USB2CAN.CAN_MSG();
|
||||||
|
_timerCanMsgBuffer[i].Data = new byte[64];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int index = 0;
|
||||||
|
foreach (var itemMsg in groupMsg)
|
||||||
|
{
|
||||||
|
foreach (var itemSignal in itemMsg)
|
||||||
|
{
|
||||||
|
// 与 StartPrecisionCycleSendMsg 保持一致的调用形态,避免与其他线程共享缓存的并发问题
|
||||||
|
CAN_DBCParser.DBC_SetSignalValue(DBCHandle, new StringBuilder(itemMsg.Key), new StringBuilder(itemSignal.SignalName), itemSignal.SignalCmdValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将信号同步到 CAN 帧
|
||||||
|
CAN_DBCParser.DBC_SyncValueToCANMsg(DBCHandle, new StringBuilder(itemMsg.Key), _timerMsgPtr);
|
||||||
|
_timerCanMsgBuffer[index] = (USB2CAN.CAN_MSG)Marshal.PtrToStructure(_timerMsgPtr, typeof(USB2CAN.CAN_MSG));
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发送 CAN 数据
|
||||||
|
int sendedNum = USB2CAN.CAN_SendMsg(DevHandle, WriteCANIndex, _timerCanMsgBuffer, (uint)_timerCanMsgBuffer.Length);
|
||||||
|
IsSendOk = sendedNum >= 0;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
IsSendOk = false;
|
||||||
|
LoggerService?.Info($"定时器周期发送CAN数据异常: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
// 标记回调完成并调度下一次
|
||||||
|
System.Threading.Interlocked.Exchange(ref _sendTimerRunningFlag, 0);
|
||||||
|
ScheduleNextTick();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算并调度下一次触发时间(基于绝对时间进行校准,减少漂移)
|
||||||
|
private void ScheduleNextTick()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!IsCycleSend) return;
|
||||||
|
var timer = _sendTimer;
|
||||||
|
if (timer == null) return;
|
||||||
|
|
||||||
|
long now = _sendTimerWatch.ElapsedTicks;
|
||||||
|
long periodTicks = (long)(_sendTimerPeriodMs * TicksPerMs);
|
||||||
|
|
||||||
|
if (_sendTimerNextTicks == 0)
|
||||||
|
{
|
||||||
|
_sendTimerNextTicks = now + periodTicks;
|
||||||
|
}
|
||||||
|
else if (now >= _sendTimerNextTicks)
|
||||||
|
{
|
||||||
|
// 若已错过计划时间,跳至最近的未来时刻,避免累计漂移
|
||||||
|
long missed = (now - _sendTimerNextTicks) / periodTicks + 1;
|
||||||
|
_sendTimerNextTicks += missed * periodTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
long dueTicks = _sendTimerNextTicks - now;
|
||||||
|
int dueMs = dueTicks <= 0 ? 0 : (int)(dueTicks / TicksPerMs);
|
||||||
|
|
||||||
|
timer.Change(dueMs, System.Threading.Timeout.Infinite);
|
||||||
|
}
|
||||||
|
catch (ObjectDisposedException)
|
||||||
|
{
|
||||||
|
// 计时器已被释放,忽略
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
LoggerService?.Info($"定时器调度异常: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
#region 精确发送报文数据
|
#region 精确发送报文数据
|
||||||
|
|
||||||
@@ -1256,7 +1484,7 @@ namespace CapMachine.Wpf.CanDrive
|
|||||||
{
|
{
|
||||||
IsSendOk = true;
|
IsSendOk = true;
|
||||||
//Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex:{(byte)0} -- MsgIndex:{(byte)(0)} ");
|
//Console.WriteLine($"Update CAN Schedule Success -- SchTabIndex:{(byte)0} -- MsgIndex:{(byte)(0)} ");
|
||||||
|
monitorValueLog.UpdateValue3(ret);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1480,6 +1708,9 @@ namespace CapMachine.Wpf.CanDrive
|
|||||||
StopSchedule();
|
StopSchedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 确保定时器发送被停止并释放资源
|
||||||
|
try { StopTimerCycleSendMsg(); } catch { }
|
||||||
|
|
||||||
// 等待接收任务结束后释放非托管缓冲区,避免并发释放
|
// 等待接收任务结束后释放非托管缓冲区,避免并发释放
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user