C# Winform freezing on SerialPort.Close

2020-05-27 04:24发布

问题:

I have a winform program that does some asynchronous IO on a SerialPort. However, I'm periodically running into an issue with the program freezing on the SerialPort.Close() call, seemingly at random.

I think it's a thread safety issue, but I'm not sure how to fix it if it is. I tried adding/removing the async DataReceived handler with the port open/close functions and discarding the in and out buffers on the port, but it doesn't seem to do anything. I think the important SerialPort code is below:

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.
    }
  }
}

UPDATE

Thanks to @Afrin's answer pointing out the deadlock condition with the UI thread (This blog post does a good job describing it, and gives several other good tips), I made a simple change, and haven't been able to reproduce the error yet!

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.
}

回答1:

The reason it would hang when you close it is because in the event handler of your SerialPort object

You're synchronizing a call with the main thread (typically by calling invoke). SerialPort's close method waits for its EventLoopRunner thread which fires DataReceived/Error/PinChanged events to terminate. but since your own code in the event is also waiting for main thread to respond, you run into a dead lock situation.

solution: use begininvoke instead of invoke: https://connect.microsoft.com/VisualStudio/feedback/details/202137/serialport-close-hangs-the-application

reference: http://stackoverflow.com/a/3176959/146622



回答2:

I had a same problem. I solved this problem by using SerialPortStrem library. You can install by Nuget Pageckage Installer.

SerialportStream libary has the following advantages.

  • An independent implementation of System.IO.Ports.SerialPort and SerialStream for better reliability and maintainability.

After using SerialPortStream library, I hadn't UI freezing problem such as deadlock in WPF. I think the same issue in Windows forms. so, use the SerialPortStream library.

This library is obviously a solution to solve the UI Freezing.



回答3:

Workaround by nobugz user from here:

1) add System.Threading.Thread CloseDown; field to form with serial port serialPort1;

2) implement method CloseSerialOnExit() with serialPort1 close steps:

private void CloseSerialOnExit()
{

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

    }
}

3) when you need to close serialPort1 (e.g. on button click) call CloseSerialOnExit() in new thread to avoid hang:

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

and that's it!



回答4:

Thank you also for answers. I faced similar issue until today. I used the Andrii Omelchenko solution and helped a lot but not 100%. I saw that the cause for hanging serial port is the reception event handler. Before stopping serial port, uninstall reception event handler.

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);
        }