Unity中SerialPort同时读写Arduino异常问题求助
串口同时读写阻塞问题的原因与解决方法
问题原因
你的代码出现读线程被写操作阻塞的核心原因是SerialPort类的实例不是线程安全的,读线程和写线程同时直接访问同一个SerialPort对象时,会引发资源竞争,导致读操作被挂起,直到写操作完成。
另外还有两个细节问题加重了这个情况:
- 读线程中调用了
stream.BaseStream.Flush():这个方法用于刷新输出缓冲区,读操作完全不需要调用它,属于无效操作。 - 写线程中每次Write后立即调用Flush并Sleep,长时间占用串口资源,进一步加剧了线程竞争。
解决方案
要实现安全的同时读写,核心是对SerialPort的所有访问操作加锁,确保同一时间只有一个线程能操作串口。具体步骤如下:
- 在Serial类中添加全局锁对象,用于同步所有串口操作。
- 读、写操作的代码块都用这个锁包裹,避免并发访问。
- 移除读线程中多余的Flush操作。
- 按需优化写操作的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
相关产品推荐
相关产品推荐

