Последовательный порт USB отключен, но все еще находится в списке портов

Итак, у меня есть код, который обнаруживает устройства, подключенные с помощью метода

SerialPort.GetPortNames();

В любом случае, все работает хорошо, кроме случаев, когда я открываю существующий порт (выбрав его из списка)

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

Тогда если устройство отключено, оно не удаляется из списка.. Я думаю, что это потому, что порт не закрыт..

Но я не могу понять, почему он все еще находится в списке, если я не закрываю его вручную, даже если устройство отключено.

port.Close();

Потому что, если я открою порт, которого нет в списке, он не появится в списке...

Может кто-нибудь объяснить мне это поведение?

3 ответа

Решение

Это полностью зависит от драйвера устройства USB, который эмулирует последовательный порт. Отключение порта, когда он открыт, вообще очень плохая идея. Существует множество драйверов, которые заставляют порт исчезать, даже если у вашего объекта SerialPort есть дескриптор, открытый на порту. Это приводит к аварийному завершению рабочего потока, который генерирует события DataReceived, PinChanged и ErrorReceived. Исключение не перехватывается, потому что оно возникает в рабочем потоке, завершающем вашу программу. Некоторые драйверы даже отклоняют попытку закрыть порт, что делает невозможным чистое завершение вашей программы.

Похоже, у вас есть приличный драйвер, который поддерживает эмулируемый порт, пока вы не вызываете Close(). Это хорошо, а не проблема. Не рассчитывайте на то, что это работает на компьютере вашего пользователя, вы не можете предсказать, какой драйвер они получат на своем устройстве. Рекомендация о покупке - хорошая идея.

Короче говоря, последовательные порты являются очень примитивными устройствами, которые датируются каменным веком компьютеров. Для них нет поддержки plug and play, поэтому происходящее совершенно непредсказуемо. Единственное, что нужно делать - никогда не отсоединять кабель во время использования устройства. Это не сложно сделать:) Подробнее о том, какие проблемы это вызывает в этом ответе.

Эта тема может быть интересной: COM-порт исчезает при отключении USB. Вы пытались избавиться от объекта SerialPort?

Это может быть эффект устаревших данных, потому что SerialPort все еще использует этот com-порт (он не удаляется, реестр не обновляется и т. д.):

Имена портов получены из системного реестра (например, HKEY_LOCAL_MACHINE\HARDWARE\DEVICEMAP\SERIALCOMM). Если реестр содержит устаревшие или иным образом неверные данные, метод GetPortNames вернет неверные данные.

Когда вы используете адаптер USB-последовательный порт, когда он отключен, вы начнете получать "Отказ в доступе" или что-то вроде этого исключения, если вы попытаетесь записать что-то в открытый перед com-портом. Вы можете попробовать тогда Close это и потом GetPortNames должен вернуть правильный список.

Sinatr ответил правильно, это устаревшие данные в реестре, которые остаются устаревшими до закрытия открытого порта и освобождения ресурсов.

SerialPort.Close()сигнализирует об освобождении ресурсов, но вам, вероятно, придется принудительно выполнить сборку мусора. (Мне пришлось для моего приложения.)

Так что-то вроде:

//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;

РЕДАКТИРОВАТЬ:

Итак, оказалось, что это было ужасно непоследовательно даже на одной машине. Библиотека Nuget SerialPortStream - это независимая реализация MicrosoftSerialPort, и изящно отлавливает все ошибки, которые у меня были, кроме обнаружения, когда USB-устройство было отключено.

Мое решение теперь проверяет, когда USB-устройство снова подключено, что очевидно, когда есть повторяющиеся записи в SerialPortStream.GetPortNames(). Закрытие порта полностью закрывает его, поэтому больше нет необходимости вызывать сборщик мусора.

Я использую следующую функцию для регулярной проверки подключенных последовательных портов:

    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());
                }
            }
        }
    }
Другие вопросы по тегам