【问题标题】:Constantly reading from a serial port with a background thread使用后台线程不断从串口读取
【发布时间】:2011-02-22 06:57:27
【问题描述】:

由于串行端口通信是异步的,我很早就发现我的项目涉及与 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

足够简单。但正如我提到的,我希望在新数据到达时不断检查该端口是否有新数据。理想情况下,只要有数据等待,我就会让操作系统告诉我。 Whaddaya 知道,我的祈祷得到了回应,有一个DataReceived 活动提供!

self.port.DataReceived += self.OnDataReceived

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

可惜,这毫无价值,because this event isn't guaranteed to be raised

不保证每收到一个字节都会引发 DataReceived 事件。

那么回到编写侦听器线程。我使用BackgroundWorker 很快就完成了这项工作,它只是一遍又一遍地调用port.ReadExisting()。它在字节进入时读取它们,当它看到一行结尾(\r\n)时,它将读取的内容放入linebuffer。然后我程序的其他部分查看linebuffer,看看是否有完整的行等待使用。

现在,这显然是一个经典的生产者-消费者问题。生产者是BackgroundWorker,将完整的行放入linebuffer。消费者是一些代码,它尽可能快地使用来自linebuffer 的那些行。

但是,消费者有点低效。现在他一直在查看linebuffer,每次都发现它是空的;虽然偶尔会发现里面有一条线等着。优化这一点的最佳方法是什么,以便消费者只有在有可用线路时才醒来?这样消费者就不会不停地访问linebuffer,这可能会引入一些并发问题。

另外,如果有更简单/更好的方式从串行端口不断读取,我愿意接受建议!

【问题讨论】:

    标签: .net python ironpython serial-port


    【解决方案1】:

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

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

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

    【讨论】:

    • 我明白了 - ReadExisting 会在那个时间点给我所有可用的字节吗?如果是,那么 DataReceived 实际上会很有用
    • 是的,ReadExisting 应该做你想做的事。但请确保您仔细阅读文档,ReadExisting 不保证给您所有字节,它特别保持“根据编码读取所有立即可用的字节”。
    • 那么,在那个时间点读取缓冲区中所有个可用字节的万无一失的方法是port.Read(buffer, 0, port.BytesToRead),我猜?
    【解决方案2】:

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

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

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

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

    【讨论】:

      【解决方案3】:

      下面是一些 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 的速度处理串行端口数据。

      【讨论】:

      • 这是一个很好的例子,几乎完全完成了我的制作人正在做的事情。你如何处理并发,即。当一个或多个消费者需要访问 rcvQ 来读取一行时?
      猜你喜欢
      • 1970-01-01
      • 2020-06-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-05-24
      相关资源
      最近更新 更多