2015-11-13 314 views
5

我正在修改一个基于C#的UI,它与一个小型PIC微控制器测试设备接口。C#:读取串行端口 - BytesToRead

UI由两个按钮组成,它们通过串口连接向微控制器发送“命令”来启动测试。每隔250毫秒,用户界面就会轮询串行接口,寻找包含PIC测试结果的简短消息。该消息显示在文本框中。

我继承的代码如下:

try 
{ 
    btr = serialPort1.BytesToRead; 
    if (btr > 0) 
     Thread.Sleep(300); 
    btr = serialPort1.BytesToRead; 
    if (btr > 0) 
    { 
     Thread.Sleep(300); 
     btr = serialPort1.BytesToRead; 

     numbytes = serialPort1.Read(stuffchar, 0, btr); 
     for (x = 0; x < (numbytes); x++) 
     { 
      cc = (stuffchar[x]); 
      stuff += Convert.ToString(Convert.ToChar((stuffchar[x]))); 
     } 

什么将是第几行由三个调用BytesToRead和两个300毫秒睡眠电话前最后读串口的原理是什么?除非我错误地解释代码,否则从串口读取任何成功消息都将花费超过600毫秒,这对我来说似乎很奇怪。

回答

0

如果该代码最初处于循环状态,它可能是一种等待PIC收集数据的方式。

如果你有真正的硬件测试我会sugest你删除睡觉。

4

这是对SerialPort.Read()的行为的一个可怕的破解。它只返回实际接收的字节数。通常只有1或2个,串行端口速度很慢,现代PC速度非常快。所以通过调用Thread.Sleep(),代码延迟了UI线程足够长的时间以使Read()调用返回更多字节。无论协议是什么样子,希望它们都可以。通常工作,并不总是。在发布的代码中,它不起作用,程序员只是任意拖延了两次。啊。

当然,最大的不幸是UI线程在强迫睡眠时非常紧张。非常明显,它的绘制速度非常缓慢并且对用户输入做出响应。

这需要通过首先注意协议来修复。 PIC需要在其响应中发送固定数量的字节,因此您可以简单地对它们进行计数,或者让PC检测是否收到完整的响应。通常通过发送一个唯一的字节作为响应的最后一个字节(SerialPort.NewLine),或者通过在响应开始时将响应的长度作为一个字节值包含在内。具体的建议很难给出,你根本没有描述协议。

您可以保留hacky代码并将其移至工作线程中,以免影响用户界面如此糟糕。您可以从SerialPort.DataReceived事件中免费获得一个。但这往往会产生两个问题,而不是解决核心问题。

0

@TomWr你是对的,从我正在阅读的是这种情况。
你下面有我的评语摘录:

try 
{ 
    // Let's check how many bytes are available on the Serial Port 
    btr = serialPort1.BytesToRead; 

    // Something? Alright then, let's wait 300 ms. 
    if (btr > 0) 
     Thread.Sleep(300); 

    // Let's check again that there are some bytes available the Serial Port 
    btr = serialPort1.BytesToRead; 

    // ... and if so wait (maybe again) for 300 ms 
    // Please note that, at that point can be all cumulated about 600ms 
    // (if we actually already waited previously) 
    if (btr > 0) 
    { 
     Thread.Sleep(300); 
     btr = serialPort1.BytesToRead; 

     numbytes = serialPort1.Read(stuffchar, 0, btr); 
     for (x = 0; x < (numbytes); x++) 
     { 
      // Seems like a useless overhead could directly use 
      // an Encoding and ReadExisting method() of the SerialPort. 
      cc = (stuffchar[x]); 
      stuff += Convert.ToString(Convert.ToChar((stuffchar[x]))); 
     } 

我的猜测是同它上面通过idstam已经述及,基本上可能要检查是否是数据由设备发送和获取它们

你可以用适当的SerialPort方法轻松重构这段代码,实际上有更好更简洁的方法来检查串口上是否有数据可用。

而不是“我正在检查端口上有多少字节,如果有什么,然后我等待300毫秒,然后再次相同的事情。“这很可悲地结束于
”所以是2倍300毫秒= 600毫秒,或只是一次(取决于是否有第一次“,或者什么也没有(取决于你通过这个用户界面进行通信的设备)因为Thread.Sleep会阻止用户界面......)。“

首先让我们考虑一段时间,您试图保持尽可能多的相同的代码库,为什么不等待600ms?

或者为什么不只是使用ReadTimeout属性和捕捉超时异常,不是那么干净,但至少在可读性方面更好,而且您直接获取字符串而不是使用一些Convert.ToChar()调用...

我感觉代码已经从C或C++(或者至少是背后的原理)移植到了主要是嵌入式软件背景的人身上。

无论如何,回到可用字节检查的数量,我的意思是除非串行端口数据在另一个线程/ BackgroundWorker/Task处理程序中刷新,否则我没有看到任何检查它的两个原因,特别是以它的方式编码。
为了让它更快?不是真的,如果串行端口上实际存在数据,则会有额外延迟。这对我来说没有多大意义。

使您的代码片段稍微好一点的另一种方法是使用ReadExisting()进行轮询。

否则,您也可以考虑使用SerialPort BaseStream的异步方法。

总而言之,如果不访问代码库的其余部分,即上下文,很难说。

如果您有更多关于目标/协议的信息,它可以提供一些关于如何做的提示。否则,我可以说这似乎是编码不好,再一次,脱节。

我甚至把汉斯提到的关于用户界面响应的问题翻了一番,因为我真的希望你的代码片段在一个不是用户界面的线程中运行(尽管你在帖子中提到用户界面正在轮询我仍然希望该片段是为另一名工人)。

如果这确实是UI线程,那么每次有Thread.Sleep调用都会阻止它,这会导致UI不能真正响应用户交互,并且可能会给最终用户带来一些挫折感。

可能还需要订阅DataReceived事件并执行处理程序所需的操作(例如,使用缓冲区和比较值等)。

请注意mono还没有实现这个事件的触发器,但是如果你正在运行一个简单的MS .NET实现,这是完美的没有多线程的麻烦。

简而言之:

  • 检查哪个线程(S)为被照顾您的片断和心灵有关的UI响应
    • 如果是UI,然后通过使用另一个线程线程,BackgroundWorker(线程池)或任务。
    • 流异步方法,以避免UI线程同步的
  • 尝试的麻烦,看目标是否真的值得双300毫秒线程睡眠方法调用
  • ,您可以直接获取字符串代替如果后者检查正在使用以执行操作而不是自己收集字节(如果所选编码可以满足您的需要),则可以采集数据。
+0

流异步方法(例如'port.BaseStream.ReadAsync')不使用另一个线程....这是件好事。您不必担心从多个线程同步对数据结构的访问,或者在线程之间编组UI。每当事件处理程序返回或调用“await”时,只需将数据结构保持一致即可。 –

+0

@BenVoigt http://referencesource.microsoft.com/#mscorlib/system/io/stream.cs,e224b4bec8748849 async/await正在使用Task,所以Threadpool。因此它可以在另一个线程上运行,但不一定。 如果你认为我错了,随意解释一下。 同意特别是当它与用户界面相关时。 – Ehouarn

+1

任务对象默认在当前上下文中运行,在UI应用程序中,默认上下文是UI消息分派器。没有使用额外的线程。现在,您可以选择将任务强制为工作线程,但这对于I/O尤其是串行I/O来说很愚蠢。工作线程仅对CPU绑定操作有利。 –