【问题标题】:SerialPort.BaseStream.ReadAsync drops or scrambles bytes when reading from a USB Serial Port从 USB 串行端口读取时,SerialPort.BaseStream.ReadAsync 丢弃或加扰字节
【发布时间】:2017-01-21 10:00:34
【问题描述】:

编辑:我已经添加了发送代码和我收到的输出示例。


我正在从连接到嵌入式系统的 USB“虚拟”串行端口读取数据。我写了两种接收数据的方法,一种是同步的,一种是异步的。同步的工作,异步的会丢失或打乱一点输入数据。我不知道为什么第二个失败了。

有效的方法调用 SerialPort.Read 并将读取超时设置为零,并请求接收缓冲区中的所有内容。我检查返回值以查看实际读取了多少字节,然后将数据放入循环缓冲区以供其他地方使用。此方法由定时器中断调用,它可以完美地接收串行数据(通常以高于 1.6 Mbps 的速率,不会丢失数据)。但是,轮询计时器对我来说已经成为一个问题,我更愿意在我的其余代码中异步接收数据。

丢失数据的方法在串口 BaseStream 上等待 ReadAsync 并循环直到取消。这种方法有点有效,但它经常无序返回数据包的前导字节,相当频繁地丢失一个字节(大约每几千个数据字节一次),偶尔会丢失数百个连续字节从一个数据包。

这里可能存在两个完全不同的问题,因为丢失更大块数据似乎与更高的数据速率和更重的系统活动相关。问题的特定部分可能是由于缓冲区溢出造成的——可能是由于 USB 调度程序遇到延迟时 USB 握手失败——但我在这里展示的示例仅在 50 处传输了非常少量的数据毫秒间隔,系统空闲,除了这个测试例程。

我观察到 ReadAsync 经常在一次读取时返回数据包的第一个字节,并在下一次读取时返回数据包的其余部分。我相信这是预期的行为,因为 MSDN 说如果在一段时间内没有数据可用,ReadAsync 将返回它收到的第一个字节。但是,我认为这种行为在某种程度上与我的问题有关,因为当单个字节丢失或乱序时,它“始终”是第一个字节,其余的数据包正常到达。

当数据包很小时,数据包前面的“丢失”字节通常(但不总是)似乎在下一次读取在数据包的其余部分之后传递,而这对我来说完全没有意义。对于较大的数据包,这种情况偶尔仍会发生,但更常见的是,当数据包较大时,第一个字节会丢失。

我进行了广泛的搜索,并阅读了我能找到的关于这个主题的每一个 SO 问题。我发现其他人似乎有类似的问题(例如:SerialPort.BaseStream.ReadAsync missing the first byte),但没有人有任何可接受甚至合理的解决方案。

Ben Voigt (http://www.sparxeng.com/blog/software/must-use-net-system-io-ports-serialport) 和其他似乎真正了解串行通信的人都建议在 basestream 上使用 ReadAsync,而微软的 IOT 团队也推荐了这种方法,所以我不得不相信这种方法 应该 工作。

问题 1: 为什么我的代码在 USB 串行 BaseStream 上使用 ReadAsync 会丢弃/加扰字节?

问题 2: 如果 ReadAsync 不能可靠地以正确的顺序返回所有接收到的字节,我可以在传统的 SerialPort.Read 周围放置一个异步包装器并等待/循环它吗我不必从计时器轮询?我读到这是一个坏主意,但我也读到 SerialPort 类在内部是异步的,所以也许这样可以吗?还是我唯一的选择是把它放在工作线程上,让它把所有时间都花在等待上?

我的代码如下。我已经设置了serialPort1.ReadTimeout = 0; 和serialPort1.BaseStream.ReadTimeout = 0;(并且我尝试了其他持续时间)。 我启用了 RTS 和 DTR,因为这是一个 USB_serial 端口,它应该在内部处理握手,而且当我同步读取时它确实会这样做——但当我从 BaseStream 读取时可能不是这样?

这里是第一种方法:

// this method works perfectly when called from a timer.
// SerialPort.ReadTimeout must be set to zero for this to work.
// It handles incoming bytes reliably at rates above 1.6 Mbps.

private void ReadSerialBytes()
{
    if (!serialPort1.IsOpen)
        return;

    if (serialPort1.BytesToRead > 0)
    {
        var receiveBuffer = new byte[serialPort1.ReadBufferSize];

        var numBytesRead = serialPort1.Read(receiveBuffer, 0, serialPort1.ReadBufferSize);
        var bytesReceived = new byte[numBytesRead];
        Array.Copy(receiveBuffer, bytesReceived, numBytesRead);

        // Here is where I audit the received data.
        // the NewSerialData event handler displays the 
        // data received (as hex bytes) and writes it to disk.
        RaiseEventNewSerialData(bytesReceived);

        // serialInBuffer is a "thread-safe" global circular byte buffer 
        // The data in serialInBuffer matches the data audited above.
        serialInBuffer.Enqueue(bytesReceived, 0, numBytesRead);
    }
}

这是第二种方法,Edited 删除@Lucero 指出的尾递归。现在我不会耗尽内存 :) 但原来的数据丢失问题当然仍然存在。

// This method is called once after the serial port is opened,
// and it repeats until cancelled. 
// 
// This code "works" but periodically drops the first byte of a packet, 
// or returns that byte in the wrong order.
// It occasionally drops several hundred bytes in a row.
private async Task ReadSerialBytesAsync(CancellationToken ct)
{
    while((!ct.IsCancellationRequested) && (serialPort1.IsOpen))
    {
        try
        {
            serialPort1.BaseStream.ReadTimeout = 0;
            var bytesToRead = 1024;
            var receiveBuffer = new byte[bytesToRead];
            var numBytesRead = await serialPort1.BaseStream.ReadAsync(receiveBuffer, 0, bytesToRead, ct);

            var bytesReceived = new byte[numBytesRead];
            Array.Copy(receiveBuffer, bytesReceived, numBytesRead);

             // Here is where I audit the received data.
             // the NewSerialData event handler displays the 
             // data received (as hex bytes) and writes it to disk.
             RaiseEventNewSerialData(bytesReceived);

            // serialInBuffer is a "thread-safe" global circular byte buffer 
            // The data in serialInBuffer matches the data audited above.
            serialInBuffer.Enqueue(receiveBuffer, 0, numBytesRead);
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error in ReadSerialBytesAsync: " + ex.ToString());
            throw;
        }
    }
}

这是来自发送系统的 C++ 代码(带有 ARM 芯片的 teensy 3.2)。 它发送从 00 到 FF 的字节序列,每 50 毫秒重复一次。

 void SendTestData()
 {
    byte asyncTestBuffer[256] = { 0 };
    for (int i = 0; i < 256; i++)
        asyncTestBuffer[i] = i;

    while(true)
    {
    Serial.write(asyncTestBuffer, sizeof(asyncTestBuffer));
    delay(50);
    }
}

传统的同步 SerialPort.Read(从计时器调用)完全按预期接收每个块,没有数据丢失。它看起来像这样,一遍又一遍:

=====
32 msec => Received 256 bytes 
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====

现在这是 SerialPort.BaseStream.ReadAsync 接收的内容。在另一个版本中,我附加了一个终端数据包序列号,以证明当我看到一个零后跟另一个零时,它们之间并没有真正丢失一个完整的数据包。数据包序列号都存在,所以前导字节确实似乎丢失或乱序传递。

7 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
5 msec => Received 1 bytes 
00
=====
55 msec => Received 1 bytes 
00
=====
4 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
42 msec => Received 1 bytes 
00
=====
5 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
68 msec => Received 1 bytes 
00
=====
7 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
31 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
9 msec => Received 1 bytes 
00
=====
33 msec => Received 1 bytes 
00
=====
10 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
55 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
12 msec => Received 1 bytes 
00
=====
12 msec => Received 1 bytes 
00
=====
15 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
68 msec => Received 255 bytes 
0102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====
16 msec => Received 1 bytes 
00
=====
14 msec => Received 256 bytes 
000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F202122232425262728292A2B2C2D2E2F303132333435363738393A3B3C3D3E3F404142434445464748494A4B4C4D4E4F505152535455565758595A5B5C5D5E5F606162636465666768696A6B6C6D6E6F707172737475767778797A7B7C7D7E7F808182838485868788898A8B8C8D8E8F909192939495969798999A9B9C9D9E9FA0A1A2A3A4A5A6A7A8A9AAABACADAEAFB0B1B2B3B4B5B6B7B8B9BABBBCBDBEBFC0C1C2C3C4C5C6C7C8C9CACBCCCDCECFD0D1D2D3D4D5D6D7D8D9DADBDCDDDEDFE0E1E2E3E4E5E6E7E8E9EAEBECEDEEEFF0F1F2F3F4F5F6F7F8F9FAFBFCFDFEFF
=====

我花了几个星期来追踪这个问题,它最初表现为正在开发的产品的奇怪行为。我很确定我一定做错了什么,但我只是看不到它,此时我非常渴望任何想法或建议!

【问题讨论】:

  • 什么是serialInBuffer?您的 async 方法不应使用共享状态,尤其是在这不是线程安全的情况下。另外,尾递归是个坏主意……
  • @Lucero 代码块顶部的 cmets 解释说 serialInBuffer 是循环字节缓冲区类的一个实例。我将编辑以说明它是线程安全的,但这不是问题。我通过在接收到的数据进入serialInBuffer 之前引发一个事件来审核它。我将注释代码以表明这一点。
  • @Lucero 为什么在这种情况下尾递归是个坏主意?我最初是在一个 While (true) 循环中使用它,但对其进行了更改,使其看起来更像 Ben Voigt 发布的内容,并使逻辑可见,而无需在代码块之间跳转。
  • 因为这会为任务对象积累内存,直到完成。 stackoverflow.com/questions/31966833/tail-recursion-with-tasks
  • @Lucero 好的,感谢您提出这个问题。我已经更改了该代码,但正如预期的那样,数据丢失问题仍然存在。我添加了显示问题的输出的附加信息和示例。

标签: c# serial-port usbserial


【解决方案1】:

在逐步浏览了 .Net SerialPort 类的反编译源代码后,我终于找到了答案(只安装了 Rclick on SerialPort-&gt;Navigate-&gt;Decompiled Sources 的 resharper)。

答案 #1: 字节乱序问题是由于我的程序之前的错误造成的。我已经取消并重新启动了 readAsync 循环,但我使用了错误的取消令牌,因此有两个循环副本都在等待来自串行端口的 readAsync。两者都发出中断以返回接收到的数据,但当然这是一个竞争条件,即谁先到达那里。

答案 #2: 请注意我使用同步读取方法的方式:我不使用 Received 事件(无法正常工作)或检查要读取的字节数(这是不可靠的)或类似的东西。我只是将超时设置为零,尝试使用大缓冲区读取,并检查我返回了多少字节。

当以这种方式调用时,同步 SerialPort.Read 首先尝试从内部缓存 [1024] 接收数据字节的读取请求。如果它仍然没有足够的数据来满足请求,那么它会使用完全相同的缓冲区、(调整的)偏移量和(调整的)计数对底层 BaseStream 发出 ReadAsync 请求。

底线:按照我的使用方式使用时,同步 SerialPort.Read 方法的行为与 SerialPort.ReadAsync 完全相同。我的结论是,在同步方法周围放置一个异步包装器可能会很好,然后等待它。但是,现在我可以可靠地从基本流中读取数据,因此我不需要这样做。

更新:我现在使用包含循环的任务可靠地从我的串行端口接收超过 3Mbps,该循环不断等待 SerialPort.Basestream.ReadAsync 并将结果添加到循环缓冲区。

【讨论】:

  • 这个底线难道不是以异步被包裹在同步被包裹在异步中吗?仅使用 SerialPort.ReadASync 不是更快/更可靠吗?
  • @Foitn 如果您的意思是 SerialPort.Basestream.ReadAsync,那么是的!包装问题只是因为 Basestream.ReadAsync 导致我丢失字节,但事实证明这是竞争相同基本流的秘密额外线程。我现在在串口上使用 BaseStream.ReadAsync 可靠地接收到大约 3Mbps。
  • 这确实是我的意思,很高兴你能够修复它
  • @Craig.Feied 您有几个用户对您的完整最终代码感兴趣。你会考虑分享它吗?
  • @FrankerZ 我很乐意分享我现在使用的(终于!)稳定且高性能的代码的工作版本。由于这个问题主要是关于加扰字节,这是一个单独的问题,我将作为一个新的问答发布,并在完成后标记你。
【解决方案2】:

我知道问题被提出/解决已经有一段时间了,但在搜索时注意到了它。我之前也遇到过同样的“问题”。现在我在串口的 BaseStream 上使用 Pipereader 来处理读取。这允许我仅在收到完整消息(并同时接收多条消息)时清除传入缓冲区。而且它似乎表现得很好。

代码是这样的:

        var reader = PipeReader.Create(serial.BaseStream);
        while (!token.IsCancellationRequested)
        {
            ReadResult result = await reader.ReadAsync(token);

            // find and handle packets
            // Normally wrapped in a handle-method and a while to allow processing of several packets at once 
            // while(HandleIncoming(result))
            // {
                    result.Buffer.Slice(10); // Moves Buffer.Start to position 10, which we use later to advance the reader
            // }

            // Tell the PipeReader how much of the buffer we have consumed. This will "free" that part of the buffer
            reader.AdvanceTo(result.Buffer.Start, result.Buffer.End);

            // Stop reading if there's no more data coming
            if (result.IsCompleted)
            {
                break;
            }
        }

在此处查看管道文档:https://docs.microsoft.com/en-us/dotnet/standard/io/pipelines

【讨论】:

  • 你在没有 Pipe 和 PipeWriter 的情况下使用它吗?我找不到任何允许将流设置为管道的方法。
【解决方案3】:

我可以确认加扰序列在 NET 6 中仍然存在(或者又回来了?)。

我在 2022 年 1 月编写了我的第一个 NET 6 桌面应用程序,并且第一次遇到了这个问题。我已经使用 SerialPort 类至少 4 或 5 年了,从未遇到过这个问题。我几乎在我编写的每个应用程序中都使用它来与各种设备进行通信。

我只是知道这个问题存在很长时间了!。我看到的最早的报告是 2012 年的,……它还在吗?,真的吗?。

到目前为止,我编写的串口应用程序都是基于 NET Framework 4.7.2 和更早版本的。在这个框架中,SerialPort 是 System.dll 的一部分。在 NET 6 中,SerialPort 是一个移动到 System.IO.Ports.dll 的平台扩展,必须作为 nugget 包安装。有没有可能他们移植了一个旧的、有漏洞的版本?

在我的测试中,我有旧的 NET Framework 4.7.2 应用程序每 20 毫秒通过一个物理端口 COM3(无 USB 适配器)发送一个字符串。读取字符串的 NET 6 应用程序在同一个桌面上侦听第二个物理端口 (COM4)。两个端口由一根短的 NULL 调制解调器电缆连接,仅连接 TX、RX、GND。没有握手。这是加扰的结果,据我所知,它本质上是随机的:

<-- port open with tranmission running already -->
 fox jumps over the lazy Dog - 123456789]
[The quick brown fox jumps over the lazy Dog - 123456789]
[The quick brown[The quick brown fox jumps over the lazy Dog - 123456789]
[The quick brown fox jumps over the lazy Dog - 123456789]
[The quick brown fox jumps over the lazy Dog - 123456789]
<--- many good lines removed for brevity --->
[The quick brown fox jumps over the lazy Dog - 123456789]
[The quick brown fox jumps over he lazy Dog - 1t23456789]
[The quick brown fox jumps over the lazy Dog - 123456789]
<--- many good lines removed for brevity --->
[The quick brown fox jumps over the lazy Dog - 123456789]
[The quick brownfox jumps over  the lazy Dog - 123456789]
[The quick brown fox jumps over the lazy Dog - 123456789]

请注意,第三行包含第一行的前三个单词!。之后只有一个字节不合适。它看起来像一个草率的双缓冲区实现......

操作!我忘记了这些字节!,给你!

如果我打开第二个不错的旧终端(Net Framework 4.7.2)并收听相同的流,则输出是完美的。

在 NET 6 项目中,我使用与很久以前编写的完全相同的类来封装 SerialPort 功能,以便在项目之间使用它。

直到现在我订阅了 SerialPort.DataReceived 事件,然后在事件处理程序中读取在一个任务中是这样的(启动但没有被事件处理程序等待):

var bytesToRead = _serialPort.BytesToRead;
byte[] Data = new Byte[bytesToRead];
int received = _serialPort.Read(Data, 0, bytesToRead);
... notify the class user.

我将测试这里建议的工作...

【讨论】:

  • 不幸的是,经过密集测试后,我发现我的旧终端程序在 Net Framework 4.7.2 中使用 SerialPort 实现,它也可以乱码字符,但不像上面的示例那么怪诞。因此,我使用 C++/CLR 中的本机 Win32 系统调用实现了一个替换类,它允许与其他 .NET 语言进行互操作。如果有人发现这有任何用处,请告诉我。
  • 我不知道是不是这个问题,但你说:Until now I subscribed to the SerialPort.DataReceived event and then in the event handler the reading goes like this inside a Task (started but not waited by the event handler)。请记住,如果多次引发DataReceived,您最终将同时运行多个任务,在这种情况下,您将失去对位处理顺序的任何保证。确保添加适当的同步,这样在给定时间只有一个任务读取/处理数据
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多