You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 11:15:42