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

Unity中SerialPort同时读写Arduino异常问题求助

串口同时读写阻塞问题的原因与解决方法

问题原因

你的代码出现读线程被写操作阻塞的核心原因是SerialPort类的实例不是线程安全的,读线程和写线程同时直接访问同一个SerialPort对象时,会引发资源竞争,导致读操作被挂起,直到写操作完成。

另外还有两个细节问题加重了这个情况:

  • 读线程中调用了stream.BaseStream.Flush():这个方法用于刷新输出缓冲区,读操作完全不需要调用它,属于无效操作。
  • 写线程中每次Write后立即调用Flush并Sleep,长时间占用串口资源,进一步加剧了线程竞争。

解决方案

要实现安全的同时读写,核心是对SerialPort的所有访问操作加锁,确保同一时间只有一个线程能操作串口。具体步骤如下:

  1. 在Serial类中添加全局锁对象,用于同步所有串口操作。
  2. 读、写操作的代码块都用这个锁包裹,避免并发访问。
  3. 移除读线程中多余的Flush操作。
  4. 按需优化写操作的Flush时机(无需每次Write都调用Flush,除非有强实时性要求)。

修改后的Serial类

using System;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
using System.IO.Ports;

public class Serial : MonoBehaviour
{
    public SerialPort stream = new SerialPort();
    public string receivedString = "";
    Thread readThread;
    private static List<Action> executeOnMainThread = new List<Action>();
    // 用于同步串口操作的锁对象
    private readonly object serialLock = new object();

    public void StartConnection()
    {
        stream.PortName = "COM4";
        stream.BaudRate = 9600;
        stream.ReadTimeout = 600000;
        stream.WriteTimeout = 600000;
    
        if (!stream.IsOpen)
        {
            stream.Open();
            readThread = new Thread(ReadFromSerial);
            readThread.Start();
        }
    }

    void ReadFromSerial()
    {
        while (stream.IsOpen)
        {
            try
            {
                // 读操作加锁,避免与写操作竞争
                lock (serialLock)
                {
                    string value = stream.ReadLine();
                    Debug.Log(value);
                    
                    // 将UI更新逻辑加入主线程执行队列
                    lock (executeOnMainThread)
                    {
                        executeOnMainThread.Add(() => {
                            receivedString = value;
                            // 此处可添加UI更新代码
                        });
                    }
                }
            }
            catch (TimeoutException)
            {
                // 捕获超时异常,防止读线程崩溃
                continue;
            }
        }
    }

    // 提供线程安全的串口写入方法
    public void WriteToSerial(string data)
    {
        if (!stream.IsOpen) return;
        
        lock (serialLock)
        {
            stream.Write(data);
            // 按需调用Flush,非必要可注释
            // stream.BaseStream.Flush();
        }
    }

    void Update () 
    {
        lock (executeOnMainThread)
        {
            foreach (var action in executeOnMainThread)
            {
                action();
            }
            executeOnMainThread.Clear();
        }
    }  
}

修改后的SimulationHandler类

using System;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
using System.IO;

public class SimulationHandler : MonoBehaviour
{
    [SerializeField] GameObject serialobject;
    private Serial serial;
    
    string presureCSVFileName = "";
    List<string> presureData = new List<string>();

    Thread simPThread;

    void Start()
    {
        serial = serialobject.GetComponent<Serial>(); 
    }
    
    public void SelectCSV()
    {
        presureCSVFileName = "C:/Users/Pc/Downloads/simp.csv";
        GetPresureData();
    }

    void GetPresureData()
    {
        using (StreamReader reader = new StreamReader(presureCSVFileName))
        {
            while (!reader.EndOfStream)
            {
                string line = reader.ReadLine();
                string[] cells = line.Split(',');
                presureData.Add(cells[3]);
            }
        }
    }

    void SIMPThreadFunction()
    {
        for (int i = 0; i < presureData.Count ; i++)
        {
            if (serial.stream.IsOpen)
            {
                string simpData = presureData[i];
                // 调用线程安全的写入方法
                serial.WriteToSerial(simpData);
                Debug.Log(simpData);
                Thread.Sleep(1000);
            }     
        }
        Debug.Log("SIMP done!");
    }

    public void SIMP()
    {
        Debug.Log("SIMP start!");
        simPThread = new Thread(SIMPThreadFunction);
        simPThread.Start();
    }
}

额外说明

  • 加锁后,读和写操作会按顺序排队执行,不会再出现互相阻塞的情况,串口的同时读写能力可以正常发挥。
  • 读操作中添加了TimeoutException的捕获,避免因串口超时导致读线程崩溃。
  • 写操作通过调用Serial类提供的线程安全方法访问串口,避免直接操作stream引发资源竞争。

内容的提问来源于stack exchange,提问作者ayşenur tak

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 11:25:10