Как сделать надежное программирование SerialPort с .NET / C #? - PullRequest
26 голосов
/ 14 января 2009

Я пишу Windows Service для связи с Serial Mag-Stripe Reader и релейной платой (система контроля доступа).

У меня возникают проблемы, когда код перестает работать (я получаю 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, чтобы попытаться открыть порт будет успешным после нескольких попыток.

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

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

Есть какие-нибудь идеи относительно того, что вызывает это?

Ответы [ 9 ]

24 голосов
/ 05 июля 2010

@ thomask

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

4 голосов
/ 14 января 2009

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

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);
}
2 голосов
/ 28 января 2009

Как сделать надежную асинхронную связь

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

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

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

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

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

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

2 голосов
/ 14 января 2009

Вы пытались оставить порт открытым в вашем приложении и просто включить / выключить 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, поэтому не знаю, сработает ли это.

1 голос
/ 14 января 2009

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

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

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

  3. Остановите HyperTerminal, я проверяю, что он правильно закрыт, используя диспетчер задач

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

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

  6. Я прекращаю вышеупомянутое приложение.

  7. VOILA, мой сервис снова включается, порт успешно открывается, и светодиод горит / выключается.

1 голос
/ 14 января 2009

Этот код работает правильно. Я протестировал его на своем локальном компьютере в консольном приложении, используя 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();
            }
        }
    }
1 голос
/ 14 января 2009

Я попытался изменить рабочий поток, как этот, с точно таким же результатом. Как только 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();
    }
}
0 голосов
/ 14 января 2009

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

0 голосов
/ 14 января 2009

Этот ответ должен был стать комментарием ...

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...