.NET 7 C# SerialPort信号量超时过期问题的解决与规避
问题现象
在C# ASP.NET Core 7 Web API项目中操作虚拟串口读写时,偶尔会触发IOException,错误提示为**“信号量超时时间已过期”**,且出现该错误后,除非重启连接设备,否则SerialPort似乎无法恢复。
异常栈追踪
System.IO.IOException: The semaphore timeout period has expired. : 'COM7'
at System.IO.Ports.SerialStream.EndWrite(IAsyncResult asyncResult)
at System.IO.Ports.SerialStream.Write(Byte[] array, Int32 offset, Int32 count, Int32 timeout)
at System.IO.Ports.SerialPort.Write(Byte[] buffer, Int32 offset, Int32 count)
当前代码实现
SerialPort实例化代码
if (_serialPort == null && SerialPort.GetPortNames().Contains(poortNaam)) { _serialPort = new SerialPort(_poortNaam, _baudrate, _parity, _dataBits, _stopBits); _serialPort.WriteTimeout = 500; _serialPort.ReadTimeout = 500; _serialPort.ReadBufferSize = 8192; _serialPort.WriteBufferSize = 8192; _serialPort.RtsEnable = true; Semaphore.Wait(); if (_serialPort.IsOpen) { _serialPort.Close(); Thread.Sleep(250); } _serialPort.Open(); Thread.Sleep(500); Semaphore.Release(); }
写入方法逻辑
try { _serialPort.DiscardInBuffer(); _serialPort.DiscardOutBuffer(); if (!_serialPort.IsOpen) { _serialPort.Open(); Thread.Sleep(250); } Trace.Write("Verstuur: "); foreach (var item in frame) { Trace.Write($"{item},"); } _serialPort.Write(frame, 0, frame.Length); } catch (IOException ioe) { Trace.WriteLine(ioe.ToString()); _serialPort.Close(); Thread.Sleep(500); _serialPort.Open(); Thread.Sleep(500); _serialPort.Write(frame, 0, frame.Length); }
读取方法逻辑
byte incomingByte; while(!timeout && !MessageStop) { try { int IncomingBytes = _serialPort.BytesToRead; for (int i = 0; i < IncomingBytes ; i++) { incomingByte = (byte)_serialPort.ReadByte(); IncomingBytesList.Add(incomingByte ); if (incomingByte == Ex || (incomingByte == Nak && IncomingBytes == 1) || (incomingByte == Ak && IncomingBytes == 1)) { MessageStop = true; break; } } } catch (IOException ioe) { Trace.WriteLine(ioe.Message); _serialPort.Close(); Thread.Sleep(100); _serialPort.Open(); Thread.Sleep(200); } }
已尝试的方案
- 使用SemaphoreSlim限制线程访问代码
- 在串口开闭操作间添加延迟
- 尝试重新实例化SerialPort对象
需求
推测问题由SerialPort辅助线程卡住导致超时,需解决:
- 如何最佳处理/恢复该问题?
- 如何避免触发该问题?
解决方案
一、问题处理与恢复优化
彻底销毁旧实例,重新创建SerialPort
出现超时异常后,直接关闭旧实例并释放资源,不要复用,重新创建新的SerialPort对象。因为原实例的内部辅助线程可能已经卡死,复用会导致问题持续。修改异常处理逻辑:catch (IOException ioe) { Trace.WriteLine(ioe.ToString()); // 彻底释放旧实例资源 if (_serialPort != null) { try { _serialPort.Close(); } catch {} _serialPort.Dispose(); _serialPort = null; } Thread.Sleep(1000); // 重新初始化串口 InitializeSerialPort(); // 调用你的实例化逻辑方法 // 重试写入/读取 if (_serialPort != null && _serialPort.IsOpen) { _serialPort.Write(frame, 0, frame.Length); } }添加重试次数限制
不要无限重试,设置最大重试次数(比如3次),避免陷入死循环:int retryCount = 0; const int maxRetries = 3; bool writeSuccess = false; while (!writeSuccess && retryCount < maxRetries) { try { // 写入逻辑 _serialPort.Write(frame, 0, frame.Length); writeSuccess = true; } catch (IOException ioe) { retryCount++; Trace.WriteLine($"写入失败,重试第{retryCount}次:{ioe.Message}"); // 销毁并重建串口 // ... 上述销毁重建代码 ... } } if (!writeSuccess) { Trace.WriteLine("写入重试多次失败,请检查设备状态"); // 触发告警或记录错误日志 }
二、避免触发问题的优化措施
避免频繁开关串口
当前代码中写入方法每次都检查是否打开并可能重新打开,频繁的开闭会增加线程冲突和资源泄漏风险。建议串口初始化后保持打开状态,除非明确检测到连接异常再关闭重建。优化线程同步机制
原代码中Semaphore的使用存在问题:在实例化时获取信号量,但如果初始化过程中抛出异常,可能导致信号量无法释放。改用SemaphoreSlim并配合try/finally确保释放:private readonly SemaphoreSlim _serialSemaphore = new SemaphoreSlim(1, 1); // 在初始化或操作串口时 await _serialSemaphore.WaitAsync(); try { // 串口操作逻辑:打开、读写等 } finally { _serialSemaphore.Release(); }避免同步读写阻塞主线程
ASP.NET Core是异步框架,同步的SerialPort读写会阻塞线程池线程,增加线程竞争风险。改用异步读写方式,或者将串口操作放到单独的后台线程/Task中执行:// 异步写入示例 public async Task WriteAsync(byte[] frame) { await _serialSemaphore.WaitAsync(); try { if (!_serialPort.IsOpen) { _serialPort.Open(); await Task.Delay(250); } _serialPort.DiscardInBuffer(); _serialPort.DiscardOutBuffer(); // 模拟异步写入(SerialPort本身无原生Async,可包装成Task) await Task.Run(() => _serialPort.Write(frame, 0, frame.Length)); } finally { _serialSemaphore.Release(); } }调整超时时间与缓冲区设置
当前500ms的超时时间对于虚拟串口可能过短,可适当调整为1000ms。同时,避免频繁调用DiscardInBuffer和DiscardOutBuffer,除非明确需要清除缓存,否则会打断正常的数据传输流程。监控串口状态
定期检查串口连接状态,比如通过发送心跳包确认设备是否在线,一旦检测到异常立即销毁并重建串口,避免问题积累。
内容的提问来源于stack exchange,提问作者Kasper

