使用后台线程不断从串行端口读取数据

发布于 2024-10-02 17:43:59 字数 1444 浏览 1 评论 0原文

由于串行端口通信是异步的,我在涉及与 RS 232 设备通信的项目的早期就发现,我必须有一个后台线程不断读取端口接收的数据。现在,我使用 IronPython (.NET 4.0),因此我可以访问 .NET 中内置的灵活 SerialPort 类。这让我可以编写这样的代码:

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 中。然后我的程序的其他部分查看行缓冲区以查看是否有任何完整的行等待使用。

显然,这是一个典型的生产者-消费者问题。生产者是 BackgroundWorker,将完整的行放入 linebuffer 中。消费者是一些尽可能快地消耗行缓冲区中的行的代码。

然而,消费者的效率有点低。现在,他不断地检查行缓冲区,每次发现它是空的时都会感到失望;尽管每隔一段时间就会发现里面有人排队。优化此功能的最佳方法是什么,以便消费者仅在有空闲线路时醒来?这样,消费者就不会不断地访问行缓冲区,这可能会引入一些并发问题。

另外,如果有一种更简单/更好的方法来不断地从串行端口读取数据,我愿意接受建议!

As serial port communication is asynchronous, I figured out early on into my project involving communication with a RS 232 device that I will have to have a background thread constantly reading the port for data received. Now, I'm using IronPython (.NET 4.0) so I have access to the slick SerialPort class built into .NET. This lets me write code like this:

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

Simple enough. But as I mentioned I want to be constantly checking this port for new data as it arrives. Ideally, I would have the OS tell me anytime there's data waiting. Whaddaya know, my prayers have been answered, there is a DataReceived event provided!

self.port.DataReceived += self.OnDataReceived

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

Too bad this is worthless, though, because this event isn't guaranteed to be raised!

The DataReceived event is not guaranteed to be raised for every byte received.

So back to writing a listener thread, then. I've accomplished this rather quickly with a BackgroundWorker that just calls port.ReadExisting() over and over again. It reads bytes as they come in, and when it sees a line ending (\r\n), it places what it read into a linebuffer. Then other parts of my program look at the linebuffer to see if there are any complete lines waiting to be used.

Now, this is a classic producer-consumer problem, obviously. The producer is the BackgroundWorker, placing complete lines into linebuffer. The consumer is some code that consumes those lines from the linebuffer as fast as possible.

However, the consumer is sort of inefficient. Right now he's constantly checking the linebuffer, getting disappointed each time to find it empty; though every once in a while does find a line waiting inside. What's the best way to optimize this so that the consumer only wakes up when there is a line available? That way the consumer isn't spinning around constantly accessing the linebuffer, which might introduce some concurrency issues.

Also, if there is a simpler/better way of reading constantly from a serial port, I'm open to suggestions!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

唠甜嗑 2024-10-09 17:43:59

我不明白为什么您不能使用 DataReceived 事件。正如文档所述

DataReceived 事件不是
保证每个字节都会升高
已收到。使用 BytesToRead 属性
确定还剩多少数据
在缓冲区中读取。

这就是说,不能保证您为每个单独的数据字节获得单独的事件。您可能需要使用 BytesToRead 属性检查端口上是否有多个可用字节,并读取相应的字节数。

I don't see why you can't use the DataReceived event. As the docs state

The DataReceived event is not
guaranteed to be raised for every byte
received. Use the BytesToRead property
to determine how much data is left to
be read in the buffer.

All that is saying is that you aren't guaranteed to get a separate event for each individual byte of data. You may need to check and see if there more than one byte available on the port using the BytesToRead property and read the corresponding number of bytes.

烈酒灼喉 2024-10-09 17:43:59

不需要有轮询串行端口的旋转线程。

我建议使用 SerialPort.BaseStream.BeginRead(...) 方法。这比尝试使用 SerialPort 类中的事件要好得多。对 BeginRead 的调用立即返回并注册一个异步回调,该回调在读取完成后调用。在回调方法中,您调用 EndRead 并返回读入提供的缓冲区的字节数。 BaseStream(SerialStream)继承自Stream并遵循.Net Streams的通用模式,这是非常有用的。

但请务必记住,它是一个调用回调的 .Net 线程,因此您需要快速处理数据,或者将任何繁重的工作交给您自己的线程之一。我强烈建议阅读以下链接,特别是备注部分。

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

There is no need to have a spinning thread that polls the serial port.

I suggest using SerialPort.BaseStream.BeginRead(...) method. It is much better than trying to use the event in the SerialPort class. The call to BeginRead returns right away and registers an asynchronous callback that is invoked once a read is complete. In the callback method, you call EndRead and that returns the number of bytes read into the supplied buffer. The BaseStream(SerialStream) inherits from Stream and follows the general pattern for .Net Streams, which is very useful.

But it's important to keep in mind that it is a .Net thread that invokes the callback, so you need to processes the data fast, or pass off any heavy lifting to one of your own threads. I highly suggest reading the following link, particularly the Remarks section.

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

各自安好 2024-10-09 17:43:59

下面是一些 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

就其价值而言,与此非常相似的代码已以接近 1Mbps 的速度处理串行端口数据。

Here is some VB code that expresses my opinion on how this should be done:

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

For what it is worth, code very similar to this has processed serial port data at near 1Mbps.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文