C#Winform在SerialPort.Close上冻结

本文关键字:冻结 Close SerialPort C#Winform | 更新日期: 2023-09-27 18:20:20

我有一个winform程序,它在SerialPort上执行一些异步IO。然而,我在SerialPort.Close()调用上周期性地遇到程序冻结的问题,这似乎是随机的。

我认为这是一个线程安全问题,但我不确定如何解决。我尝试添加/删除带有端口打开/关闭功能的异步DataReceived处理程序,并丢弃端口上的输入和输出缓冲区,但似乎没有任何作用。我认为重要的SerialPort代码如下:

using System;
using System.Collections.Generic;
using System.IO.Ports;
public class SerialComm
{
  private object locker = new object();
  private SerialPort port;
  private List<byte> receivedBytes;
  
  public SerialComm(string portName)
  {
    port = new SerialPort(portName);
    port.BaudRate = 57600;
    port.Parity = Parity.None;
    port.DataBits = 8;
    port.StopBits = StopBits.One;
    
    receivedBytes = new List<byte>();
  }
  public void OpenPort()
  {
    if(port!=null && !port.IsOpen){
      lock(locker){
        receivedBytes.Clear();
      }
      port.DataReceived += port_DataReceived;
      port.Open();
    }
  }
  public void ClosePort()
  {
    if(port!=null && port.IsOpen){
      port.DataReceived -= port_DataReceived;
      while(!(port.BytesToRead==0 && port.BytesToWrite==0)){
        port.DiscardInBuffer();
        port.DiscardOutBuffer();
      }
      port.Close();
    }
  }
  private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
  {
    try{
      byte[] buffer = new byte[port.BytesToRead];
      int rcvdBytes = port.Read(buffer, 0, buffer.Length);
      lock(locker){
        receivedBytes.AddRange(buffer);
      }
      //Do the more interesting handling of the receivedBytes list here.
    } catch (Exception ex) {
      System.Diagnostics.Debug.WriteLine(ex.ToString());
      //put other, more interesting error handling here.
    }
  }
}

更新

由于@Afrin的回答指出了UI线程的死锁情况(这篇博客文章很好地描述了它,并给出了其他几个好的提示),我做了一个简单的更改,但还无法重现错误!

private void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
  try{
    byte[] buffer = new byte[port.BytesToRead];
    int rcvdBytes = port.Read(buffer, 0, buffer.Length);
    
    lock(locker){
      receivedBytes.AddRange(buffer);
    }
    
    ThreadPool.QueueUserWorkItem(handleReceivedBytes);
    
  } catch (Exception ex) {
    System.Diagnostics.Debug.WriteLine(ex.ToString());
    //put other, more interesting error handling here.
  }
}
private void handleReceivedBytes(object state)
{
  //Do the more interesting handling of the receivedBytes list here.
}

C#Winform在SerialPort.Close上冻结

关闭它时它会挂起的原因是因为在SerialPort对象的事件处理程序中

您正在与主线程同步调用(通常通过调用invoke)。SerialPort的关闭方法等待其EventLoopRunner线程终止,该线程触发DataReceived/Error/PinChanged事件。但是,由于事件中您自己的代码也在等待主线程响应,因此您会遇到死锁的情况。

解决方案:使用begininvoke而不是invoke:https://connect.microsoft.com/VisualStudio/feedback/details/202137/serialport-close-hangs-the-application

参考:http://stackoverflow.com/a/3176959/146622

编辑:Microsoft链接已断开,因为他们已停用Connect。请尝试web.archive.org:https://web.archive.org/web/20111210024101/https://connect.microsoft.com/VisualStudio/feedback/details/202137/serialport-关闭挂起应用程序

nobugz用户从这里解决问题:

1) 添加System.Threading.Thread CloseDown;字段与串口serialPort1形成;

2) 用serialPort1关闭步骤实现方法CloseSerialOnExit()

private void CloseSerialOnExit()
{
    try
    {
        serialPort1.DtrEnable = false;
        serialPort1.RtsEnable = false;
        serialPort1.DiscardInBuffer();
        serialPort1.DiscardOutBuffer();
        serialPort1.Close();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

3) 当你需要关闭serialPort1(例如点击按钮)时,在新线程中调用CloseSerialOnExit()以避免挂起:

...
CloseDown = new System.Threading.Thread(new System.Threading.ThreadStart(CloseSerialOnExit)); 
CloseDown.Start();
...

就这样!

我也遇到了同样的问题。我使用SerialPortStream库解决了这个问题。您可以通过Nuget软件包安装程序进行安装。

SerialportStream库具有以下优点。

  • System.IO.Ports.SerialPort和SerialStream的独立实现,可提高可靠性和可维护性

在使用了SerialPortStream库之后,我没有遇到诸如WPF中死锁之类的UI冻结问题。我认为Windows窗体中也存在同样的问题。因此,请使用SerialPortStream库。

这个库显然是解决UI冻结的一个解决方案。

也感谢您的回答。直到今天,我还面临着类似的问题。我使用了Andrii Omelchenko解决方案,帮助很大,但不是100%。我发现挂起串行端口的原因是接收事件处理程序。在停止串行端口之前,请卸载接收事件处理程序。

try
        {
            serialPort.DtrEnable = false;
            serialPort.RtsEnable = false;
            serialPort.DataReceived -= serialPort_DataReceived;
            Thread.Sleep(200);
            if (serialPort.IsOpen == true)
            {
                serialPort.DiscardInBuffer();
                serialPort.DiscardOutBuffer();
                serialPort.Close();
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }