C# Winform freezing on SerialPort.Close
Asked Answered
O

5

11

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.
}
Overmodest answered 12/1, 2012 at 22:29 Comment(0)
G
18

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: https://mcmap.net/q/1014246/-c-proper-way-to-close-serialport-with-winforms

EDIT: the Microsoft link is broken as they have retired Connect. try web.archive.org: https://web.archive.org/web/20111210024101/https://connect.microsoft.com/VisualStudio/feedback/details/202137/serialport-close-hangs-the-application

Giddings answered 12/1, 2012 at 22:32 Comment(8)
To make sure I understand you correctly, in summary, there's a deadlock within the SerialPort between the Read and the Close calls?Overmodest
you have to change the way you invoke UI element updates in the port_DataReceived event handler, use BeginInvoke to update instead of Invoke, or as described in the solution use another thread to handle the event.Giddings
Handling the data for the UI on a different thread seems to have solved the problem. Thanks!Overmodest
Most of your post is copied verbatim from Zach Saw's answer.Stradivari
Very weird... I'm sure others before you were able to see the link and MS has changed the location. btw, solution is to use begininvoke instead of invoke and the link was an answer from a Microsoft Tech giving the same advise. ps: as a windows developer I assume we all need a MS account... don't we?!Giddings
Beside this using a thread to close handle will remove any problem there. As posted HereBurnt
Link rot on the Microsoft link.Overmeasure
from web.archive.org: web.archive.org/web/20111210024101/https://…Giddings
F
4

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!

Fandango answered 6/8, 2018 at 16:7 Comment(1)
But this would only redirect the hanging to another thread. The port will still remain open. What will happen if I (or some other program) try to use the port again?Berkly
C
2

I had a same problem. I solved this problem by using SerialPortStream library. You can install by Nuget Package 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.

Carefree answered 12/9, 2018 at 1:2 Comment(1)
This fixed so many bugs I had to keep fighting with, on top of providing a buffered read/write interface without worrying if all the bytes were written or if converting char types corrupted the stream.Hold
F
2

I had this problem and spend many time for answer. It be fixed by closing port by this methode:

if (serialPort.IsOpen)
{
   serialPort.DataReceived -= SerialPort_DataReceived;
   serialPort.ErrorReceived -= SerialPort_ErrorReceived;
   serialPort.BaseStream.Close();
   serialPort.Close();
}

No one source not contain info about closing BaseStream, but it fix my problem

Frowst answered 1/9, 2023 at 15:6 Comment(0)
K
0

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);
        }
Keyboard answered 21/2, 2020 at 15:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.