C# Windows服务多COM端口异常:仅首个使用端口可响应的解决方法
问题分析与解决方案
核心问题
你的代码存在三个关键问题,导致多COM端口无法同时响应:
- SerialPort对象未被全局持有:OnStart方法中创建的SerialPort是局部变量,方法执行完毕后会被垃圾回收器回收,失去事件监听能力——只有第一个被触发的端口因临时引用暂时存活,其余端口会直接失效。
- EF上下文线程不安全:全局唯一的
sapEntities db被多个DataReceived事件线程同时调用,而EF上下文本身不支持多线程并发操作,会引发线程阻塞、死锁,进而阻塞其他端口的事件处理流程。 - 不必要的线程休眠:
Thread.Sleep(100)会阻塞SerialPort的事件处理线程,拖慢整体响应速度,甚至导致其他端口的事件被延迟或丢弃。
修复步骤
1. 全局保存SerialPort对象
在Service1类中添加线程安全集合,用于保存所有已打开的SerialPort实例,避免被GC回收:
// 线程安全集合,存储所有已打开的串口实例 private readonly ConcurrentBag<SerialPort> _openPorts = new ConcurrentBag<SerialPort>();
2. 修改OnStart方法,将SerialPort加入全局集合
修改foreach循环内的代码,把创建的SerialPort对象加入全局集合:
foreach (string portName in ports) { try { SerialPort sp = new SerialPort(portName, 9600, Parity.None, 8, StopBits.One); sp.Handshake = Handshake.RequestToSend; sp.DataReceived += DataReceivedHandler; sp.Open(); _openPorts.Add(sp); // 加入全局集合,防止被GC回收 WriteToFile($"{portName} is open at {DateTime.Now}"); } // 原有异常捕获代码保持不变 }
3. 移除Thread.Sleep,改用异步处理避免阻塞
直接读取数据并异步处理,释放SerialPort的事件线程:
private void DataReceivedHandler(object sender, SerialDataReceivedEventArgs e) { try { SerialPort sp = (SerialPort)sender; if (sp != null && sp.IsOpen) // 增加端口状态校验 { string data = sp.ReadExisting(); string cPort = sp.PortName; // 异步处理数据,避免阻塞事件线程 Task.Run(() => ProcessData(cPort, data)); } else { WriteToFile($"DataReceivedHandler error: SerialPort is not open at {DateTime.Now}"); } } catch (Exception ex) { WriteToFile($"DataReceivedHandler error: {ex.Message} at {DateTime.Now}"); } }
4. 为每个数据处理请求创建独立的EF上下文
EF上下文不能跨线程复用,每次处理数据时创建新的上下文实例:
private void ProcessData(string port, string data) { // 每次处理数据创建新上下文,保证线程安全 using (var db = new sapEntities()) { var ins_light = new real_light(); switch (port) { case "COM1": WriteToFile($"COM1 Read: {data} at {DateTime.Now}"); COMDataLog($"COM1 Read: {data}"); ins_light = new real_light { lgt_date = DateTime.Now, lgt_data = data, lgt_ref = 1 }; db.real_light.Add(ins_light); try { db.SaveChanges(); WriteToFile($"COM1 Data stored correctly: {data} at {DateTime.Now}"); } catch (Exception ex) { WriteToFile($"COM1 Error storing data: {ex.Message} at {DateTime.Now}"); } break; case "COM2": WriteToFile($"COM2 Read: {data} at {DateTime.Now}"); COMDataLog($"COM2 Read: {data}"); ins_light = new real_light { lgt_date = DateTime.Now, lgt_data = data, lgt_ref = 2 }; db.real_light.Add(ins_light); try { db.SaveChanges(); WriteToFile($"COM2 Data stored correctly: {data} at {DateTime.Now}"); } catch (Exception ex) { WriteToFile($"COM2 Error storing data: {ex.Message} at {DateTime.Now}"); } break; default: WriteToFile($"Unknown port: {port}"); break; } } }
5. 服务停止时关闭所有串口
在OnStop方法中添加代码,关闭并释放所有已打开的串口:
protected override void OnStop() { foreach (var port in _openPorts) { try { if (port.IsOpen) { port.Close(); } port.Dispose(); WriteToFile($"{port.PortName} is closed at {DateTime.Now}"); } catch (Exception ex) { WriteToFile($"Error closing {port.PortName}: {ex.Message} at {DateTime.Now}"); } } _openPorts.Clear(); }
额外注意事项
- 确保
WriteToFile和COMDataLog方法是线程安全的,可通过lock语句同步文件访问,避免多线程写入时出现乱码或文件占用问题。 - 若后续需添加串口重连逻辑,可监听SerialPort的
ErrorReceived事件,在端口断开时尝试重新打开。
内容的提问来源于stack exchange,提问作者BlackSD
相关产品推荐
相关产品推荐

