Постоянное чтение из последовательного порта с фоновым потоком - PullRequest
3 голосов
/ 13 ноября 2010

Поскольку связь через последовательный порт является асинхронной, я рано понял в своем проекте, связанном с обменом данными с устройством RS 232, что мне потребуется фоновый поток, постоянно считывающий порт для полученных данных. Теперь я использую IronPython (.NET 4.0), поэтому у меня есть доступ к классному классу SerialPort, встроенному в .NET. Это позволяет мне писать код так:

self.port = System.IO.Ports.SerialPort('COM1', 9600, System.IO.Ports.Parity.None, 8, System.IO.Ports.StopBits.One)
self.port.Open()
reading = self.port.ReadExisting() #grabs all bytes currently sitting in the input buffer

Достаточно просто. Но, как я уже говорил, я хочу постоянно проверять этот порт на наличие новых данных по мере их поступления. В идеале, я бы хотел, чтобы ОС говорила мне каждый раз, когда ожидают данные. Что бы знать, мои молитвы были услышаны, предусмотрена DataReceived встреча!

self.port.DataReceived += self.OnDataReceived

def OnDataReceived(self, sender, event):
    reading = self.port.ReadExisting()
    ...

Жаль, что это ничего не стоит, , потому что это событие не гарантированно будет вызвано !

Событие DataReceived не гарантируется для каждого полученного байта.

Итак, вернемся к написанию потока для слушателя. Я сделал это довольно быстро с BackgroundWorker, который просто вызывает port.ReadExisting() снова и снова. Он читает байты по мере их поступления, а когда он видит строку, заканчивающуюся (\r\n), он помещает то, что он прочитал, в linebuffer. Затем другие части моей программы смотрят на linebuffer, чтобы увидеть, есть ли какие-либо полные строки, ожидающие использования.

Теперь, это классическая проблема производитель-потребитель , очевидно. Производитель - BackgroundWorker, помещающий полные строки в linebuffer. Потребитель - это некоторый код, который потребляет эти строки из linebuffer как можно быстрее.

Однако потребитель вроде бы неэффективен. Прямо сейчас он постоянно проверяет linebuffer, каждый раз разочаровываясь, обнаруживая, что он пуст; хотя время от времени находит линию, ожидающую внутри. Как лучше всего оптимизировать этот процесс, чтобы потребитель проснулся только при наличии доступной линии? Таким образом, потребитель не будет постоянно обращаться к linebuffer, что может вызвать некоторые проблемы с параллелизмом.

Кроме того, если есть постоянный и более простой способ чтения с последовательного порта, я открыт для предложений!

Ответы [ 4 ]

3 голосов
/ 13 ноября 2010

Я не понимаю, почему вы не можете использовать событие DataReceived. Как утверждают документы

Событие DataReceived не является гарантированно будет поднят для каждого байта получено. Используйте свойство BytesToRead определить, сколько данных осталось быть прочитанным в буфере.

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

2 голосов
/ 22 февраля 2011

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

Я предлагаю использовать метод SerialPort.BaseStream.BeginRead (...). Это гораздо лучше, чем пытаться использовать событие в классе SerialPort. Вызов BeginRead сразу возвращается и регистрирует асинхронный обратный вызов, который вызывается после завершения чтения. В методе обратного вызова вы вызываете EndRead, и он возвращает количество байтов, прочитанных в предоставленный буфер. BaseStream (SerialStream) наследуется от Stream и следует общему шаблону для потоков .Net, что очень полезно.

Но важно помнить, что это поток .Net, который вызывает обратный вызов, поэтому вам нужно быстро обрабатывать данные или передавать любую тяжелую работу одному из ваших собственных потоков. Я настоятельно рекомендую прочитать следующую ссылку, в частности, раздел «Примечания».

http://msdn.microsoft.com/en-us/library/system.io.stream.beginread.aspx

1 голос
/ 16 ноября 2010

Вот некоторый код VB, который выражает мое мнение о том, как это должно быть сделано:

Dim rcvQ As New Queue(Of Byte()) 'a queue of buffers
Dim rcvQLock As New Object

Private Sub SerialPort1_DataReceived(ByVal sender As System.Object, _
                                     ByVal e As System.IO.Ports.SerialDataReceivedEventArgs) _
                                 Handles SerialPort1.DataReceived

    'an approach
    Do While SerialPort1.IsOpen AndAlso SerialPort1.BytesToRead <> 0
        'what if the number of bytes available changes at ANY point?
        Dim bytsToRead As Integer = SerialPort1.BytesToRead
        Dim buf(bytsToRead - 1) As Byte

        bytsToRead = SerialPort1.Read(buf, 0, bytsToRead)

        'place the buffer in a queue that can be processed somewhere else
        Threading.Monitor.Enter(rcvQLock)
        rcvQ.Enqueue(buf)
        Threading.Monitor.Exit(rcvQLock)

    Loop

End Sub

Несмотря на это, код, очень похожий на этот, обработал данные последовательного порта на скорости около 1 Мбит / с.

1 голос
/ 13 ноября 2010

Почему ваш продюсер не поднимает событие, когда полная строка помещается в LineBuffer

Кроме того, читая документы, я полагаю, что это просто говорит о том, что чтение 100 байт! = 100 возбужденных событий - это не значит, что на события нельзя положиться.

...