C#:正在读取BytesToRead

本文关键字:读取 BytesToRead | 更新日期: 2023-09-27 18:21:55

我正在修改一个基于C#的UI,该UI与一个小型PIC微控制器测试设备接口。

UI由几个按钮组成,通过串行端口连接向微控制器发送"命令"来启动测试。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毫秒以上,这对我来说似乎很奇怪。

C#:正在读取BytesToRead

这是一个围绕SerialPort.read()行为的可怕破解。它只返回实际接收的字节数。通常只有1或2个,串行端口速度较慢,而现代电脑速度非常快。因此,通过调用Thread.Sleep(),代码将UI线程延迟足够长的时间,以使Read()调用返回更多字节。希望所有人,不管协议是什么样子。通常有效,但并不总是有效。在发布的代码中,它不起作用,程序员只是任意延迟了两倍的时间。啊。

当然,最大的痛苦是UI线程在被迫睡眠时非常紧张。非常明显的是,它在绘制和响应用户输入方面变得非常缓慢。

这需要通过首先关注协议来修复。PIC需要在其响应中发送固定数量的字节,这样您就可以简单地将其计数,或者为PC提供一种检测是否收到完整响应的方法。通常通过发送一个唯一的字节作为响应的最后一个字节(SerialPort.NewLine)或在消息开始时将响应的长度作为字节值来完成。具体的建议很难给出,你根本没有描述协议。

您可以保留这些破解的代码,并将其移动到工作线程中,这样它就不会对UI产生严重影响。您可以从SerialPort.DataReceived事件中免费获得一个。但这往往会产生两个问题,而不是解决核心问题。

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

如果你有真正的硬件要测试,我建议你删除两个Sleeps。

@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毫秒,或者只有一次(取决于是否有第一次),或者可能什么都没有(取决于你通过这个UI通信的设备,自从线程以来,它可能真的很慢。睡眠会阻塞UI…)。"

首先,让我们考虑一下,您正试图保持尽可能多的相同代码库,为什么不等待600毫秒呢?

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

我感觉到这些代码是从C或C++(或者至少是背后的基本原理)移植过来的,这些人大多有嵌入式软件背景。

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

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

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

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

如果你有更多关于目标/协议的信息,它可能会给出一些关于该做什么的提示。否则,我只能说这似乎编码不好,再一次,断章取义。

我把Hans提到的关于UI响应性的内容翻了一番,甚至翻了三番,因为我真的希望你的代码片段在一个不是UI的线程中运行(尽管你在帖子中提到UI正在轮询,但我仍然希望这个代码片段是给另一个工作人员的)。

如果这真的是UI线程,那么每次出现thread.Sleep调用时,它都会被阻止,这会使UI对用户交互没有真正的响应,并可能给最终用户带来一些挫败感。

订阅DataReceived事件并使用处理程序执行您想要/需要的操作(例如,使用缓冲区和比较值等)也可能是值得的

请注意,mono仍然没有实现此事件的触发器,但如果您运行的是普通的MS.NET实现,那么这完全没有多线程的麻烦。

简而言之:

  • 检查哪个线程正在处理您的代码片段,并注意UI的响应性
    • 如果是UI,则通过thread、BackgroundWorker(Threadpool)或Task使用另一个线程
    • 流异步方法以避免UI线程同步的麻烦
  • 试着看看这些目标是否真的值得一个300毫秒的线程睡眠方法调用
  • 如果后面的检查使用so来执行操作,而不是自己收集字节(如果选择的编码可以满足您的需求),则可以直接获取字符串而不是收集