如何让程序等待串口完整接收数据?规避Thread.Sleep的实现方案
问题分析与解决方案
先帮你拆解下现有代码里的核心问题,再给你几个更可靠的实现方案:
现有代码的关键问题
- 事件逻辑完全倒置:你定义的
event_1不仅没在类里声明,而且逻辑搞反了——你是在判断数据完整后才触发event_1.Set(),但线程却在等着这个信号,等于没等数据接收就直接执行判断,自然直接返回Error。 - 串口实例不统一:
portOpenButton_Click里重新new了一个SerialPort对象,但类里绑定DataReceived事件的是另一个comPort实例,这会导致数据根本不会触发你写的接收逻辑! - 无等待直接判断:
SendDataAndReadData里启动线程后立刻检查port1ReceivedMessage,这时候串口数据还没来得及接收,肯定是空的,直接走错误分支。
方案一:修复线程等待逻辑(基于ManualResetEvent)
先把基础问题修正,用ManualResetEvent实现可靠的等待:
- 首先在类里声明事件对象:
private ManualResetEvent _receiveCompletedEvent = new ManualResetEvent(false);
- 修正
SendDataAndReadData方法,改为先等待事件触发再判断:
public string SendDataAndReadData() { port1ReceivedMessage = string.Empty; string sendData = "?"; Send(sendData); // 重置事件,让线程进入等待状态 _receiveCompletedEvent.Reset(); // 等待2秒(可根据串口速率调整),超时则返回错误 if (_receiveCompletedEvent.WaitOne(2000)) { return port1ReceivedMessage; } return "Error"; }
- 修改
SetText方法,当检测到完整的\r\n结尾时触发事件:
private void SetText(string text) { port1ReceivedMessage += text; // 检查是否收到完整的结束符 if (port1ReceivedMessage.EndsWith("\r\n")) { // 通知等待的线程:数据已接收完成 _receiveCompletedEvent.Set(); } }
- 修正串口打开逻辑,确保用同一个实例:
private void portOpenButton_Click(object sender, EventArgs e) { // 复用类里已声明的comPort实例,不要重新new comPort.PortName = "COM4"; comPort.BaudRate = 9600; comPort.Parity = Parity.None; comPort.DataBits = 8; comPort.StopBits = StopBits.One; comPort.ReadTimeout = 2000; if (!comPort.IsOpen) { comPort.Open(); } }
方案二:更优雅的异步实现(基于TaskCompletionSource)
如果想避免显式线程管理,用异步/await模式会更简洁:
- 在类里声明TaskCompletionSource对象:
private TaskCompletionSource<string> _receiveTaskSource;
- 改写为异步方法:
public async Task<string> SendDataAndReadDataAsync() { port1ReceivedMessage = string.Empty; string sendData = "?"; Send(sendData); _receiveTaskSource = new TaskCompletionSource<string>(); // 等待任务完成或超时 var completedTask = await Task.WhenAny(_receiveTaskSource.Task, Task.Delay(2000)); if (completedTask == _receiveTaskSource.Task) { return _receiveTaskSource.Task.Result; } return "Error"; }
- 修改
SetText触发任务完成:
private void SetText(string text) { port1ReceivedMessage += text; if (port1ReceivedMessage.EndsWith("\r\n")) { _receiveTaskSource?.TrySetResult(port1ReceivedMessage); _receiveTaskSource = null; // 重置,避免重复触发 } }
- 修改按钮点击事件调用异步方法:
private async void sendButton_Click(object sender, EventArgs e) { string result = await SendDataAndReadDataAsync(); // 这里可以把结果显示到UI,比如textBox1.Text = result; }
额外注意事项
- 数据拆分处理:如果串口可能分多次发送同一条消息的片段,你需要持续拼接缓存,直到检测到
\r\n;如果有连续多条消息,建议用Split('\n')分割后逐行处理,记得去掉每行末尾的\r。 - 异常处理:要补充串口打开失败、发送失败、超时等场景的异常捕获,避免程序崩溃。
- 资源释放:析构函数里的
comPort.Close()不够可靠,建议在Form_FormClosing事件里关闭串口并释放资源。
内容的提问来源于stack exchange,提问作者Json
相关产品推荐
相关产品推荐

