【问题标题】:C# Winform freezing on SerialPort.CloseC# Winform 在 SerialPort.Close 上冻结
【发布时间】:2022-04-02 05:08:08
【问题描述】:

我有一个在SerialPort 上执行异步 IO 的 winform 程序。但是,我会定期遇到程序在 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 线程的死锁情况(This blog post 很好地描述了它,并提供了其他几个很好的提示),我做了一个简单的更改,但无法重现错误呢!

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# .net serial-port


    【解决方案1】:

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

    您正在将调用与主线程同步(通常通过调用调用)。 SerialPort 的 close 方法等待触发 DataReceived/Error/PinChanged 事件的 EventLoopRunner 线程终止。但是由于你自己在事件中的代码也在等待主线程响应,所以你遇到了死锁的情况。

    解决方案:使用 begininvoke 而不是调用: 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-close-hangs-the-application

    【讨论】:

    • 为了确保我正确理解您,总之,SerialPortReadClose 调用之间存在死锁?
    • 您必须更改在 port_DataReceived 事件处理程序中调用 UI 元素更新的方式,使用 BeginInvoke 而不是 Invoke 进行更新,或者如解决方案中所述使用另一个线程来处理事件。
    • 在不同的线程上处理 UI 的数据似乎已经解决了这个问题。谢谢!
    • 您的大部分帖子都是从Zach Saw's answer逐字复制的。
    • 很奇怪...我敢肯定,在您看到链接之前,其他人已经更改了位置。顺便说一句,解决方案是使用 begininvoke 而不是调用,该链接是来自 Microsoft Tech 的回答,给出了相同的建议。 ps:作为一个windows开发者,我假设我们都需要一个MS帐户……不是吗?!
    【解决方案2】:

    来自here 的 nobugz 用户的解决方法:

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

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

    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();
    ...
    

    就是这样!

    【讨论】:

    • 但这只会将挂起重定向到另一个线程。港口仍将保持开放。如果我(或其他程序)再次尝试使用该端口会怎样?
    【解决方案3】:

    我遇到了同样的问题。我通过使用SerialPortStream 库解决了这个问题。 您可以通过 Nuget Package Installer 进行安装。

    SerialportStream 库具有以下优点。

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

    使用 SerialPortStream 库后,我没有遇到 WPF 死锁等 UI 冻结问题。我认为 Windows 表单中存在同样的问题。所以,请使用 SerialPortStream 库。

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

    【讨论】:

    • 除了提供缓冲的读/写接口之外,它还修复了许多我不得不继续解决的错误,而不必担心是否所有字节都已写入,或者转换 char 类型是否会损坏流。
    【解决方案4】:

    也感谢您的回答。直到今天我都面临着类似的问题。我使用了 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);
            }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-01-02
      • 2014-12-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-24
      相关资源
      最近更新 更多