2

So I have a code which detect the devices plugged with the method

SerialPort.GetPortNames();

Anyway, everything works well except when I open an existing port (By selecting it from the list)

port = new SerialPort(portname, 9600);
port.Open();

Then if the device is unplugged, it's not removed from the list.. I think it's because the port is not closed..

But I can't figure out why it's still on the list if I don't close it manually, even though the device is unplugged..

port.Close();

Because if I open a port which is not in the list, it doesn't appear in the list then..

Can anyone explain me this behavior ?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Tofandel
  • 3,006
  • 1
  • 29
  • 48

4 Answers4

6

It is entirely up to the USB device driver that emulates the serial port. Unplugging a port while it is opened is in general a very bad idea. There are plenty of drivers that make the port disappear, even if your SerialPort object has a handle opened on the port. This tends to crash a worker thread that generates the DataReceived, PinChanged and ErrorReceived events. The exception is not catchable because it occurs on a worker thread, terminating your program. Some drivers even reject an attempt to close the port, making it impossible to end your program cleanly.

Sounds like you have a decent driver that keeps the emulated port alive as long as you don't call Close(). That's a Good Thing, not a problem. Don't count on this working on your user's machine, you cannot predict what kind of driver they'll get with their device. A buying recommendation is a good idea.

Long story short, serial ports are very primitive devices that date from the stone age of computing. There is no plug and play support for them so what happens is completely unpredictable. Only sane thing to do is never to unplug the cable while the device is in use. This is not hard to do :) More about the kind of trouble it causes in this answer.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Exactly what I was searching for (+1 for both answers) so I guess there is nothing I can do to solve this problem.. Thank you anyway – Tofandel Apr 23 '14 at 15:53
  • 1
    Sure there is. Don't unplug the device while you are using it. Just because you *can* does not make it a good idea to *do*. It works about as well as jerking a flash drive while Windows is writing to it. You just don't do that either, everybody instinctively understands that. It just isn't different for serial ports. Always use the "Safely remove hardware" command, like you do for any USB device. Which will of course tell you that it isn't safe to do. – Hans Passant Apr 23 '14 at 16:01
1

This topic might be interesting: COM port disappears when unplugging USB. Have you tried to Dispose the SerialPort object?

Community
  • 1
  • 1
renevondecafe
  • 129
  • 1
  • 7
  • I like this topic.. The thing is not to dispose the SerialPort object but try to send something to the object and if it doesn't work then it means that the port is no longer available so close it. – Tofandel Apr 23 '14 at 16:01
1

It could be stale data effect, because SerialPort is still using that com-port (it is not disposed, registry is not updated, etc):

The port names are obtained from the system registry (for example, HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM). If the registry contains stale or otherwise incorrect data then the GetPortNames method will return incorrect data.

When you are using USB-to-serial adapter, once it is unplugged you will start getting "Access denied" or something like this exception if you try to write something into opened before com-port. You could try then to Close it and then GetPortNames should return proper list.

Sinatr
  • 20,892
  • 15
  • 90
  • 319
0

Sinatr answered correctly, it is stale data in the registry that remains stale until the opened port is closed and the resources released.

SerialPort.Close() does signal to release the resources, but you probably have to force a garbage collection. (I had to for my app.)

So something like:

//EDIT: this actually isn't consistent, and I wouldn't recommend.
//      I recommend the notes following the EDIT below.
try
{
    if (port != null)
        port.Close(); //this will throw an exception if the port was unplugged
}
catch (Exception ex) //of type 'System.IO.IOException'
{
    System.GC.Collect();
    System.GC.WaitForPendingFinalizers();
}

port = null;

EDIT:

So, it turns out this was terribly inconsistent, even on the same machine. The Nuget library SerialPortStream is an independent implementation of Microsoft's SerialPort, and gracefully catches all the bugs I had except detecting when the USB device was unplugged.

My solution is now checking when the USB device is plugged back in, evident when there are duplicate entries in SerialPortStream.GetPortNames(). Closing the port fully closes it, so calling the garbage collector isn't necessary anymore.

I use the following function to routinely check the connected serial ports:

    private List<string> m_portList;
    public event EventHandler<string[]> PortListChanged;

    public void CheckForAddedDevices()
    {
        string[] portNames = SerialPortStream.GetPortNames();
        if (portNames == null || portNames.Length == 0)
        {
            if (m_portList.Count > 0)
            {
                m_portList.Clear();
                PortListChanged?.Invoke(this, null);
            }
        }
        else
        {
            if (m_portList.Count != portNames.Length)
            {
                m_portList.Clear();
                m_portList.AddRange(portNames);

                //check for duplicate serial ports (when usb is plugged in again)
                for (int i = 0; i < m_portList.Count - 1; i++)
                {
                    for (int j = i + 1; j < m_portList.Count; j++)
                    {
                        if (String.Compare(m_portList[i], m_portList[j]) == 0)
                        {
                            m_portList.Clear();
                            Close();
                        }
                    }
                }

                PortListChanged?.Invoke(this, m_portList.ToArray());
            }
            else
            {
                bool anyChange = true;
                foreach (var item in portNames)
                {
                    anyChange = true;
                    for (int i = 0; i < m_portList.Count; i++)
                    {
                        if (String.Compare(m_portList[i], item) == 0)
                        {
                            anyChange = false;
                            break;
                        }
                    }
                    if (anyChange)
                        break;
                }
                if (anyChange)
                {
                    m_portList.Clear();
                    m_portList.AddRange(portNames);

                    //check for duplicate serial ports (when usb is plugged in again)
                    for (int i = 0; i < m_portList.Count - 1; i++)
                    {
                        for (int j = i + 1; j < m_portList.Count; j++)
                        {
                            if (String.Compare(m_portList[i], m_portList[j]) == 0)
                            {
                                m_portList.Clear();
                                Close();
                            }
                        }
                    }

                    PortListChanged?.Invoke(this, m_portList.ToArray());
                }
            }
        }
    }
Jay Tennant
  • 181
  • 10