Как выполнить надежное программирование SerialPort с помощью .NET / C #?
-
22-07-2019 - |
Вопрос
Я пишу службу Windows для связи с последовательным считывателем магнитных полос и релейной платой (системой контроля доступа).
Я сталкиваюсь с проблемами, когда код перестает работать (я получаю 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 приводит к включению моего считывателя с магнитной полосой (подождите 1 секунду), завершению работы (подождите 1 секунду) и так далее.
Если я запускаю HyperTerminal и подключаюсь к тому же COM-порту, HyperTerminal сообщает мне, что порт в данный момент используется.Если я повторно нажму ENTER в HyperTerminal, чтобы попытаться повторно открыть порт, это удастся после нескольких попыток.
Это приводит к возникновению исключений IOExceptions в моем рабочем потоке, что и ожидалось.Однако, даже если я закрою HyperTerminal, я все равно получу то же самое исключение IOException в моем рабочем потоке.Единственное лекарство на самом деле - перезагрузить компьютер.
Другие программы (которые не используют .СЕТЕВЫЕ библиотеки для доступа к портам), похоже, на данный момент работают нормально.
Есть какие-нибудь идеи относительно того, что является причиной этого?
Решение
@thomask
Да, Hyperterminal действительно включает fAbortOnError в DCB SetCommState, что объясняет большинство исключений IOExceptions, генерируемых объектом SerialPort .На некоторых ПК / карманных компьютерах также есть UART, у которых по умолчанию включен флаг прерывания при ошибке, поэтому крайне важно, чтобы процедура инициализации последовательного порта очищала его (чего Microsoft не сделала).Недавно я написал длинную статью, чтобы объяснить это более подробно (см. это если вам интересно).
Другие советы
Вы не можете закрыть чье-либо соединение с портом, следующий код никогда не будет работать:
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);
}
Вы пытались оставить порт открытым в своем приложении и просто включить / выключить 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, поэтому не знаю, сработает ли это.
Как сделать надежную асинхронную связь
Не используйте методы блокировки, у внутреннего вспомогательного класса есть некоторые тонкие ошибки.
Используйте APM с классом состояния сеанса, экземпляры которого управляют буфером и буферным курсором, совместно используемыми между вызовами, и реализацией обратного вызова, которая оборачивает Когда дела идут плохо, Когда объект SerialPort закрыт (что произойдет при выходе из приложения), вполне может быть ожидающая операция ввода-вывода. Когда это так, закрытие SerialPort вызовет обратный вызов, и при этих условиях На эту архитектуру можно положиться, чтобы неожиданно не удерживать объект SerialPort. Метод restart управляет закрытием и повторным открытием объекта последовательного порта. После вызова EndRead
в try ... catch код>. В обычной работе последнее, что должен сделать блок
try
, - это установить следующий перекрывающийся обратный вызов ввода / вывода с вызовом BeginRead ()
. Р>
catch
должен асинхронно вызывать делегата для метода перезапуска. Реализация обратного вызова должна завершиться сразу после блока catch
, чтобы логика перезапуска могла уничтожить текущий сеанс (состояние сеанса почти наверняка повреждено) и создать новый сеанс. Метод restart должен not быть реализован в классе состояния сеанса, потому что это предотвратит его разрушение и воссоздание сеанса. EndRead
сгенерирует исключение, которое неотличимо от обычного дерьма в области связи. Вы должны установить флаг в своем состоянии сеанса, чтобы запретить поведение перезапуска в блоке catch
. Это предотвратит вмешательство вашего метода перезапуска в естественное отключение. Close ()
для объекта SerialPort
вызовите Thread.Sleep (5)
, чтобы дать ему возможность отпустить. Возможно, что-то еще захватит порт, поэтому будьте готовы с этим справиться, открывая его снова.
Я думаю, что пришел к выводу, что HyperTerminal не очень хорошо играет. Я выполнил следующий тест:
<Ол>Запустите мой сервис в «режиме консоли», он начнет включать / выключать устройство (это можно узнать по его индикатору).
Запустите HyperTerminal и подключитесь к порту. Устройство остается включенным (HyperTerminal поднимает DTR) Мой сервис пишет в журнал событий, что не может открыть порт
Остановите HyperTerminal, убедитесь, что он правильно закрыт с помощью диспетчера задач
Устройство остается выключенным (HyperTerminal снизил DTR), мое приложение продолжает запись в журнал событий, говоря, что не может открыть порт.
Я запускаю третье приложение (с которым мне нужно сосуществовать) и говорю ему подключиться к порту. Я так и делаю. Здесь нет ошибок.
Я остановил вышеупомянутое приложение.
VOILA, мой сервис снова включается, порт успешно открывается, и светодиод горит / выключается.
Я попытался изменить рабочий поток, как это, с тем же результатом. Однажды 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();
}
}
Этот код работает правильно. Я протестировал его на своем локальном компьютере в консольном приложении, используя 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();
}
}
}
Этот ответ должен быть длинным, чтобы стать комментарием...
Я полагаю, что когда ваша программа находится в потоке.Режим ожидания (1000) и вы открываете свое гипертерминальное соединение, гипертерминал получает контроль над последовательным портом.Затем, когда ваша программа просыпается и пытается открыть последовательный порт, выдается исключение IOException.
Измените свой метод и попробуйте обработать открытие порта по-другому.
Редактировать:О том, что вам придется перезагрузить компьютер, когда ваша программа выйдет из строя...
Вероятно, это связано с тем, что ваша программа на самом деле не закрыта, откройте свой taskmanager и посмотрите, сможете ли вы найти свой программный сервис.Обязательно остановите все ваши потоки перед выходом из приложения.
Есть ли веская причина для того, чтобы ваш сервис не имел " владения " порт? Посмотрите на встроенный сервис ИБП - как только вы скажете, что к СОМ1 подключен ИБП, вы можете поцеловать этот порт на прощание. Я бы посоветовал вам сделать то же самое, если нет строгих эксплуатационных требований для совместного использования порта.