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

C# Windows服务多COM端口异常:仅首个使用端口可响应的解决方法

问题分析与解决方案

核心问题

你的代码存在三个关键问题,导致多COM端口无法同时响应:

  1. SerialPort对象未被全局持有:OnStart方法中创建的SerialPort是局部变量,方法执行完毕后会被垃圾回收器回收,失去事件监听能力——只有第一个被触发的端口因临时引用暂时存活,其余端口会直接失效。
  2. EF上下文线程不安全:全局唯一的sapEntities db被多个DataReceived事件线程同时调用,而EF上下文本身不支持多线程并发操作,会引发线程阻塞、死锁,进而阻塞其他端口的事件处理流程。
  3. 不必要的线程休眠: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 13:42:07