C#实现两个void方法每5秒顺序调用 第二个方法不执行问题
问题根因
当前代码有两个核心问题导致getVoltage()逻辑未正常生效:
- 串口读写逻辑存在时序错误:发送串口指令后硬件返回数据存在固定延迟,代码中发送指令后立刻读取
BytesToRead属性,此时数据尚未传输到串口缓冲区,绝大多数场景下读取到的字节数为0;getPressure()未读到有效数据就立刻进入getVoltage()发送第二条指令,两次指令的返回数据会混包、丢失,最终电压数据无法被正常解析,看起来就像方法未被触发。 - 轮询逻辑不符合需求:原有
Timer采用固定5秒间隔触发模式,不会等待上一轮getPressure()、getVoltage()执行完成,一旦某次串口IO耗时超过5秒就会出现重入,多个逻辑同时操作串口引发访问冲突,也无法保证“两个方法全部执行完毕后再等待5秒开启下一轮”的时序要求。
正确实现方案
1. 修复串口读写时序问题
新增带超时机制的串口收发辅助方法,发送指令后等待硬件返回,直到读到协议约定的结束符或超时,避免发完立刻读空、数据混包的问题,同时优化两个数据读取方法的解析容错逻辑:
using System.IO.Ports; using System.Text; using System.Globalization; using System.Diagnostics; /// <summary> /// 串口发送指令并读取返回值,带超时控制 /// </summary> /// <param name="command">待发送的指令字节数组</param> /// <param name="timeoutMs">读取超时时间,单位毫秒</param> /// <returns>串口返回的字符串内容</returns> private string SendAndReadSerial(byte[] command, int timeoutMs = 300) { StringBuilder responseBuilder = new StringBuilder(); // 发送前清空缓冲区残留数据,避免历史数据混包 serialPort1.DiscardInBuffer(); serialPort1.Write(command, 0, command.Length); int elapsedWait = 0; while (elapsedWait < timeoutMs) { if (serialPort1.BytesToRead > 0) { responseBuilder.Append(serialPort1.ReadExisting()); // 协议用0x0D(回车)作为指令结尾,读到回车说明返回接收完成 if (responseBuilder.ToString().Contains('\r')) { break; } } Thread.Sleep(10); elapsedWait += 10; } return responseBuilder.ToString().Trim(); } private void getPressure() { if (!serialPort1.IsOpen) return; byte[] readCommand = { 0x50, 0x0D }; string PCR = SendAndReadSerial(readCommand); if (PCR.StartsWith("P") && PCR.Length > 15) { int tFlagIndex = PCR.IndexOf("T"); if (tFlagIndex > 3) { string pressureValStr = PCR.Substring(3, tFlagIndex - 3).Trim(); if (Decimal.TryParse(pressureValStr, NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out decimal decPCR)) { rTxtBoxPressure.Text = $"{decPCR} Torr"; if (Double.TryParse(decPCR.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out double chartData)) { serialDataChart.TriggeredUpdate(chartData); } } } } } private void getVoltage() { if (!serialPort1.IsOpen) return; byte[] readCommand2 = { 0x55, 0x0D }; string UCR = SendAndReadSerial(readCommand2); if (UCR.StartsWith("V") && UCR.Length > 15) { int volFlagIndex = UCR.IndexOf("Volts"); if (volFlagIndex > 5) { string voltageValStr = UCR.Substring(5, volFlagIndex - 5).Trim(); if (Decimal.TryParse(voltageValStr, NumberStyles.AllowExponent | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out decimal decUCR)) { rtxtBoxVoltage.Text = $"{decUCR} Volts"; if (Double.TryParse(decUCR.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out double chartData)) { serialDataChart2.TriggeredUpdate(chartData); } } } } }
2. 替换原有固定间隔Timer,实现顺序轮询逻辑
采用异步任务实现轮询,严格保证执行顺序:先执行getPressure(),完成后执行getVoltage(),两个方法全部执行完毕后等待5秒再开启下一轮,同时支持主动停止轮询,避免资源泄漏:
private CancellationTokenSource _pollCts; private void StartSerialPolling() { // 先停止已有轮询任务,避免重复启动 StopSerialPolling(); _pollCts = new CancellationTokenSource(); _ = Task.Run(async () => { while (!_pollCts.IsCancellationRequested) { try { // Winform/WPF控件操作必须切回UI线程执行 this.Invoke((Action)(() => { getPressure(); getVoltage(); })); } catch (Exception ex) { // 可在此处添加串口异常日志记录逻辑 Debug.WriteLine($"串口轮询异常:{ex.Message}"); } // 本轮执行完成后等待5秒,再进入下一轮 await Task.Delay(5000, _pollCts.Token); } }, _pollCts.Token); } /// <summary> /// 停止轮询,在窗口关闭、串口断开时调用 /// </summary> private void StopSerialPolling() { _pollCts?.Cancel(); _pollCts?.Dispose(); _pollCts = null; }
初始化时直接调用StartSerialPolling()即可启动轮询,不需要再保留原有InitTimer()和timer1_Tick()逻辑。
注意事项
- 数值解析时指定
CultureInfo.InvariantCulture,避免系统区域设置的小数点格式差异导致解析失败。 - 串口断开、窗口关闭时必须调用
StopSerialPolling()终止轮询任务,避免后台任务异常占用资源。 - 如果硬件返回数据的延迟超过300ms,可以适当调大
SendAndReadSerial方法的timeoutMs参数值。
内容的提问来源于stack exchange,提问作者Saba Soltani
相关产品推荐
相关产品推荐

