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

如何用C#多阶段任务替代循环休眠实现Arduino串口控制?

优化C#串口与Arduino伺服通信的多阶段任务实现

核心优化思路

用TaskCompletionSource<T>结合串口DataReceived事件实现无轮询的异步等待,替代原有的Thread.Sleep轮询方案,同时用异步Task串联多阶段任务,既降低资源占用,又提升响应性和线程安全性。

实现步骤与代码示例

1. 封装串口通信基础类

先封装一个串口操作类,核心是实现异步等待指定响应的方法:

using System.IO.Ports;
using System.Threading.Tasks;

public class ArduinoSerialComm
{
    private readonly SerialPort _serialPort;
    private TaskCompletionSource<string>? _responseTcs;
    private readonly object _lockObj = new object();

    public ArduinoSerialComm(string portName, int baudRate = 9600)
    {
        _serialPort = new SerialPort(portName, baudRate)
        {
            ReadTimeout = 5000,
            WriteTimeout = 5000,
            NewLine = "\r\n"
        };
        _serialPort.DataReceived += SerialPort_DataReceived;
        _serialPort.Open();
    }

    private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        lock (_lockObj)
        {
            if (_responseTcs == null) return;
            
            string response = _serialPort.ReadLine().Trim();
            // 匹配到目标响应时,完成任务
            if (_responseTcs.Task.Status != TaskStatus.RanToCompletion)
            {
                _responseTcs.SetResult(response);
            }
        }
    }

    /// <summary>
    /// 发送指令并异步等待指定响应
    /// </summary>
    /// <param name="command">要发送的指令</param>
    /// <param name="expectedResponses">等待的目标响应集合</param>
    /// <returns>实际接收到的响应</returns>
    public async Task<string> SendCommandAndWaitResponse(string command, params string[] expectedResponses)
    {
        lock (_lockObj)
        {
            if (_responseTcs != null && !_responseTcs.Task.IsCompleted)
            {
                throw new InvalidOperationException("已有未完成的等待任务,禁止并发发送指令");
            }
            _responseTcs = new TaskCompletionSource<string>();
        }

        try
        {
            _serialPort.WriteLine(command);
            string receivedResponse = await _responseTcs.Task;
            
            // 验证响应是否符合预期,不符合则抛出异常
            if (expectedResponses.Length > 0 && !expectedResponses.Contains(receivedResponse))
            {
                throw new InvalidOperationException($"收到非预期响应:{receivedResponse}");
            }
            return receivedResponse;
        }
        finally
        {
            lock (_lockObj)
            {
                _responseTcs = null;
            }
        }
    }

    public void Close()
    {
        if (_serialPort.IsOpen)
        {
            _serialPort.Close();
        }
    }
}

2. 实现多阶段异步任务(以归位流程为例)

把归位拆分为「发送归位指令→等待归位完成→发送回退指令→等待回退完成」四个异步阶段,用await依次执行:

public class ServoController
{
    private readonly ArduinoSerialComm _serialComm;

    public ServoController(ArduinoSerialComm serialComm)
    {
        _serialComm = serialComm;
    }

    /// <summary>
    /// 异步执行归位+回退完整流程
    /// </summary>
    public async Task ExecuteHomeAndRetractAsync()
    {
        // 阶段1:发送归位指令,等待"Home done"响应
        Console.WriteLine("发送归位指令...");
        await _serialComm.SendCommandAndWaitResponse("HOME", "Home done");
        Console.WriteLine("归位完成");

        // 阶段2:发送回退指令,等待"Retract done"响应
        Console.WriteLine("发送回退指令...");
        await _serialComm.SendCommandAndWaitResponse("RETRACT", "Retract done");
        Console.WriteLine("回退完成");
    }
}

3. 调用示例(主线程/UI线程均可)

class Program
{
    static async Task Main(string[] args)
    {
        using var serialComm = new ArduinoSerialComm("COM3");
        var servoController = new ServoController(serialComm);

        try
        {
            await servoController.ExecuteHomeAndRetractAsync();
            Console.WriteLine("完整流程执行完毕");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"执行出错:{ex.Message}");
        }
    }
}

关键优势说明

  • 无轮询休眠:通过DataReceived事件触发任务完成,CPU资源占用极低
  • 天然异步:用await替代线程阻塞,不会卡死UI线程(如果是WinForms/WPF应用)
  • 线程安全:通过锁控制TaskCompletionSource的并发访问,避免多实例冲突
  • 可扩展性:新增任务阶段只需添加await SendCommandAndWaitResponse调用,逻辑清晰

注意事项

  • 确保Arduino端发送的响应末尾包含串口类指定的NewLine(示例中是\r\n),否则ReadLine会无法正确读取
  • 可根据需求添加超时处理:给TaskCompletionSource设置超时,避免无限等待
  • 如果需要支持并发任务(比如控制两台电机独立操作),可以给每个电机分配独立的ArduinoSerialComm实例,或者修改通信协议加入电机标识

内容的提问来源于stack exchange,提问作者Cameron Hadland

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 18:05:17