Подтвердить что ты не робот

Как сделать надежное программирование SerialPort с .NET/С#?

Я пишу службу Windows для связи с считывателем Serial Mag-stripe и релейной панелью (системой контроля доступа).

Я сталкиваюсь с проблемами, когда код перестает работать (я получаю IOExceptions) после того, как другая программа "прервала" процесс, открыв тот же последовательный порт, что и моя служба.

Часть кода выглядит следующим образом:

public partial class Service : ServiceBase
{
    Thread threadDoorOpener;
    public Service()
    {
        threadDoorOpener = new Thread(DoorOpener);
    }
    public void DoorOpener()
    {
        while (true)
        {
            SerialPort serialPort = new SerialPort();
            Thread.Sleep(1000);
            string[] ports = SerialPort.GetPortNames();
            serialPort.PortName = "COM1";
            serialPort.BaudRate = 9600;
            serialPort.DataBits = 8;
            serialPort.StopBits = StopBits.One;
            serialPort.Parity = Parity.None;
            if (serialPort.IsOpen) serialPort.Close();
            serialPort.Open();
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
    }
    public void DoStart()
    {
        threadDoorOpener.Start();
    }
    public void DoStop()
    {
        threadDoorOpener.Abort();
    }
    protected override void OnStart(string[] args)
    {
        DoStart();
    }
    protected override void OnStop()
    {
        DoStop();
    }
}

Моя пробная программа успешно запускает рабочую нить, а открытие/закрытие и повышение DTR заставляет мой считыватель Mag-stripe включать (ждать 1сек), выключать (ждать 1 сек) и т.д.

Если я запустил HyperTerminal и подключился к тому же COM-порту, HyperTerminal сообщит, что порт используется. Если я повторно нажимаю ENTER в HyperTerminal, чтобы попытаться снова открыть порт будет успешным после нескольких попыток.

Это приводит к вызову IOExceptions в моем рабочем потоке, который ожидается. Однако, даже если я закрываю HyperTerminal, я все равно получаю то же самое IOException в своей рабочей нити. Единственное средство - фактически перезагрузить компьютер.

Другие программы (которые не используют библиотеки .NET для доступа к портам), как представляется, работают нормально на этом этапе.

Любые идеи относительно того, что вызывает это?

4b9b3361

Ответ 1

@thomask

Да, Hyperterminal фактически разрешает fAbortOnError в DCC SetCommState, что объясняет большую часть IOExceptions, создаваемых объектом SerialPort. Некоторые ПК/карманные компьютеры также имеют UART, которые по умолчанию отключили флаг ошибки, поэтому необходимо, чтобы процедура инициализации последовательного порта очистила его (что Microsoft не выполняла). Недавно я написал длинную статью, чтобы объяснить это более подробно (см. это, если вам интересно).

Ответ 2

Вы не можете закрыть какое-либо соединение elses с портом, следующий код никогда не будет работать:

if (serialPort.IsOpen) serialPort.Close();

Поскольку ваш объект не открыл порт, вы не можете его закрыть.

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

try
{
   //do serial port stuff
}
finally
{
   if(serialPort != null)
   {
      if(serialPort.IsOpen)
      {
         serialPort.Close();
      }
      serialPort.Dispose();
   }
}

Если вы хотите, чтобы процесс был прерываемым, вы должны проверить, открыт ли порт, а затем отменить его на некоторое время, а затем повторить попытку, что-то вроде.

while(serialPort.IsOpen)
{
   Thread.Sleep(200);
}

Ответ 3

Вы пытались оставить порт открытым в своем приложении и просто включить/отключить DtrEnable, а затем закрыть порт, когда ваше приложение закрывается? то есть:

using (SerialPort serialPort = new SerialPort("COM1", 9600))
{
    serialPort.Open();
    while (true)
    {
        Thread.Sleep(1000);
        serialPort.DtrEnable = true;
        Thread.Sleep(1000);
        serialPort.DtrEnable = false;
    }
    serialPort.Close();
}

Я не знаком с семантикой DTR, поэтому я не знаю, будет ли это работать.

Ответ 4

Как сделать надежные асинхронные сообщения

Не используйте методы блокировки, у внутреннего вспомогательного класса есть некоторые тонкие ошибки.

Используйте APM с классом состояния сеанса, экземпляры которого управляют буфером и буфером, разделяемым между вызовами, и реализацией обратного вызова, которая обертывает EndRead в try...catch. При нормальной работе последнее, что должен сделать блок try, - это настроить следующий перекрываемый обратный вызов ввода-вывода с вызовом BeginRead().

Когда все пошло не так, catch должен асинхронно вызывать делегат на метод перезапуска. Реализация обратного вызова должна выйти сразу после блока catch, чтобы логика перезапуска могла уничтожить текущий сеанс (состояние сеанса почти наверняка повреждено) и создать новый сеанс. Метод перезапуска не должен быть реализован в классе состояния сеанса, поскольку это предотвратит его уничтожение и воссоздание сеанса.

Когда объект SerialPort закрыт (что произойдет при выходе приложения), может быть ожидающая операция ввода-вывода. Когда это произойдет, закрытие SerialPort вызовет обратный вызов, и в этих условиях EndRead выдаст исключение, которое неотличимо от общего comms shitfit. Вы должны установить флаг в состоянии сеанса, чтобы запретить поведение перезапуска в блоке catch. Это остановит ваш метод перезапуска от вмешательства в естественное завершение работы.

В этой архитектуре можно полагаться, чтобы не удерживать объект SerialPort неожиданно.

Метод перезапуска управляет закрытием и повторным открытием объекта последовательного порта. После вызова Close() объекта SerialPort вызовите Thread.Sleep(5), чтобы дать ему возможность отпустить. Возможно, что-то еще захватит порт, поэтому будьте готовы справиться с этим, повторно открывая его.

Ответ 5

Я попытался изменить рабочий поток, как это, с тем же результатом. Как только HyperTerminal однажды завершит "захват порта" (пока мой поток спал), моя служба не сможет снова открыть порт.

public void DoorOpener()
{
    while (true)
    {
        SerialPort serialPort = new SerialPort();
        Thread.Sleep(1000);
        serialPort.PortName = "COM1";
        serialPort.BaudRate = 9600;
        serialPort.DataBits = 8;
        serialPort.StopBits = StopBits.One;
        serialPort.Parity = Parity.None;
        try
        {
            serialPort.Open();
        }
        catch
        {
        }
        if (serialPort.IsOpen)
        {
            serialPort.DtrEnable = true;
            Thread.Sleep(1000);
            serialPort.Close();
        }
        serialPort.Dispose();
    }
}

Ответ 6

Этот код работает нормально. Я тестировал его на своей локальной машине в консольном приложении, используя Procomm Plus, чтобы открыть/закрыть порт, и программа продолжает тикать.

    using (SerialPort port = new SerialPort("COM1", 9600))
    {
        while (true)
        {
            Thread.Sleep(1000);
            try
            {
                Console.Write("Open...");
                port.Open();
                port.DtrEnable = true;
                Thread.Sleep(1000);
                port.Close();
                Console.WriteLine("Close");
            }
            catch
            {
                Console.WriteLine("Error opening serial port");
            }
            finally
            {
                if (port.IsOpen)
                    port.Close();
            }
        }
    }

Ответ 7

Думаю, я пришел к выводу, что HyperTerminal не играет хорошо. Я выполнил следующий тест:

  • Запустите мой сервис в режиме консоли, он включит/выключит устройство (я могу сказать по нему светодиод).

  • Запустите HyperTerminal и подключитесь к порту. Устройство остается включенным (HyperTerminal повышает DTR) Моя служба записывает в журнал событий, что он не может открыть порт

  • Остановить HyperTerminal, я проверяю, что он правильно закрыт с помощью диспетчера задач

  • Устройство остается выключенным (HyperTerminal понизил DTR), мое приложение продолжает записывать в журнал событий, говоря, что он не может открыть порт.

  • Я запускаю третье приложение (с которым мне нужно сосуществовать) и сообщайте ему, чтобы он подключался к порту. Я делаю так. Здесь нет ошибок.

  • Я останавливаю вышеупомянутое приложение.

  • VOILA, мое обслуживание снова начинает работать, порт открывается успешно, и светодиод горит вкл/выкл.

Ответ 8

Этот ответ получил длинный комментарий...

Я считаю, что когда ваша программа находится в Thread.Sleep(1000), и вы открываете свое соединение HyperTerminal, HyperTerminal берет на себя управление последовательным портом. Когда ваша программа затем просыпается и пытается открыть последовательный порт, генерируется исключение IOException.

Перепроектируйте свой метод и попытайтесь обработать открытие порта по-другому.

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

Вероятно, потому, что ваша программа не закрыта, откройте свой диспетчер задач и посмотрите, можете ли вы найти свой программный сервис. Обязательно остановите все свои потоки перед выходом из приложения.

Ответ 9

Есть ли веская причина, чтобы ваш сервис "владел" портом? Посмотрите на встроенный сервис ИБП - как только вы скажете, что там подключен ИБП, например, COM1, вы можете поцеловать этот порт до свидания. Я бы посоветовал вам сделать то же самое, если не будет достаточно оперативных требований для совместного использования порта.