V2EX = way to explore
V2EX 是一个关于分享和探索的地方
Sign Up Now
For Existing Member  Sign In
hulalahei
V2EX  ›  C#

200 块有偿求助 C#高速读取串口,导致软件闪退的问题。

  •  
  •   hulalahei · Jun 12, 2025 · 3961 views
    This topic created in 328 days ago, the information mentioned may be changed or developed.

    开发框架是 Avaloina 11.2.2 + .NET 8.0 ,包是 System.IO.Ports 9.0.0-preview

    问题描述:电脑通过串口读取硬件的数据,设备会连续不断返回数据,如果设备每秒返回 50 次数据,软件就不会闪退。如果设备返回数据的速度超过 200 次每秒,就有一定的概率会出现闪退的问题,闪退的频率大概就是 1-2 小时出现一次,偶尔会 10 分钟内连续出现两次。我换过不同的设备,只要是读取速度过快,都会出现相同的问题。

    读取数据的代码片段是:

    _serialPort = new SerialPort();
    _serialPort.Parity = Parity.None;
    _serialPort.DataBits = 8;
    _serialPort.StopBits = StopBits.One;
    _serialPort.RtsEnable = true;
    _serialPort.Handshake = Handshake.None;
    _serialPort.ReadTimeout  = 5;  
    _serialPort.WriteTimeout = 5;
    _serialPort.ReceivedBytesThreshold = 1;
    _serialPort.DataReceived += OnSerialDataReceived;
    
    private  async void OnSerialDataReceived(object sender, SerialDataReceivedEventArgs e){
       var buffer = new byte[512]; // 最多读 10 字节
       int numRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length);
    }
    
    

    闪退的时候报错信息:

    [06/02/2025 01:20:02] InvalidOperationException: Argument_NativeOverlappedAlreadyFree
       at System.Threading.ThreadPoolBoundHandle.OnNativeIOCompleted(IntPtr instance, IntPtr context, IntPtr overlappedPtr, UInt32 ioResult, UIntPtr numberOfBytesTransferred, IntPtr ioPtr)
    

    第一个帮助我解决问题的小伙伴,200 红包感谢。

    23 replies    2025-06-26 01:27:23 +08:00
    muyiluop
        1
    muyiluop  
       Jun 12, 2025
    int numRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length);
    这里为啥要异步读呢
    luojianxhlxt
        2
    luojianxhlxt  
       Jun 12, 2025
    我现在都是用 TouchSocket

    要不你试试?

    https://touchsocket.net/docs/current/serialportclient
    hulalahei
        3
    hulalahei  
    OP
       Jun 12, 2025
    @muyiluop 我之前也尝试过直接 read ,可以正常读数,但是一会读数就卡死了,_serialPort.BytesToRead 会不断上升。正常的数据是 5 个字节的,有的时候会突然一下返回几百个字节。
    Jscroop
        4
    Jscroop  
       Jun 12, 2025
    Timeout 有调整大点试过吗?也有可能是 async 又返回 void 的问题,可以这样试试看

    void _serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
    var buffer = new byte[512]; // 最多读 10 字节
    int numRead = _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length).GetAwaiter().GetResult();
    }
    muyiluop
        5
    muyiluop  
       Jun 12, 2025
    @hulalahei #3 如果你是连续不断的接收数据的话,可以不管每次读到多少,直接丢给处理方法去处理,这里只管读。你的处理方法再去按 5 个字节或者你的数据结果去处理。返回几百个是因为你的 buffer 长度就是 512 。串口的数据他是数据流的概念,不像 udp 那种数据包的概念,发送端发了一包,接收端就接收 1 包
    hahiru
        6
    hahiru  
       Jun 12, 2025   ❤️ 1
    DataReceived 事件触发的速度超过了 OnSerialDataReceived 方法中 ReadAsync 完成的速度。导致前一个 ReadAsync 还没结束下一个 DataReceived 事件又触发了,从而尝试在同一个 BaseStream 上发起第二次 ReadAsync 。
    简单修复就是加锁,遇到竞争就丢弃此次事件,等待下一次。
    GeruzoniAnsasu
        7
    GeruzoniAnsasu  
       Jun 12, 2025
    根本原因是缓冲区和吞吐量太小,导致同步读会堆积数据,异步读会导致回调没完成就重入了

    为什么每次回调不直接读 BytesToRead 个字节?
    695975931
        8
    695975931  
       Jun 12, 2025
    有个博主对 c# 的 debug 很有经验,你可以加他看看。他的公众号:一线码农聊技术。v:aXhjaHVhbmc=
    hulalahei
        9
    hulalahei  
    OP
       Jun 12, 2025
    @GeruzoniAnsasu 回调直接读 BytesToRead 个字节我也试过,还是会报同样的错。
    hulalahei
        10
    hulalahei  
    OP
       Jun 12, 2025
    @GeruzoniAnsasu 还有个问题,就是同步读数据堆积了,无法获取到实时的数据。
    hifeng
        11
    hifeng  
       Jun 12, 2025
    类里面建个队列,ConcurrentQueue<byte[]> datas =new ConcurrentQueue<byte[]>(2000)
    回调直接读 BytesToRead 到 buff,
    添加到队列 datas.Enqueue(buff) 就返回;
    另外一个线程去处理 datas;
    hulalahei
        12
    hulalahei  
    OP
       Jun 12, 2025
    @muyiluop #5 意思是把读和处理分开吗?我记得我试过只读数据,压根不处理,也会造成数据的积累。比如改成这样之后

    int toRead = _serialPort.BytesToRead;

    if (toRead <= 0) return;

    var buffer = new byte[toRead]; // 最多读 10 字节
    int numRead = _serialPort.Read(buffer, 0, buffer.Length);
    正常是返回 5 字节,一开始会是正常 5 5 5 5 5 5 5 这样返回,后续就会变成 5 5 5 120 160,然后越来越大。
    hulalahei
        13
    hulalahei  
    OP
       Jun 12, 2025
    @hifeng 我试试
    hulalahei
        14
    hulalahei  
    OP
       Jun 12, 2025
    @hahiru 好的,我试试。
    andykuen959595
        15
    andykuen959595  
       Jun 12, 2025
    @695975931 #8 哈哈哈 大佬都发朋友圈了
    FreeWong
        16
    FreeWong  
       Jun 12, 2025
    DataReceived 事件在同一时间只能引发一个,如果你的设备发送的数据过快,而你在 DataReceived 事件中去做协议完整性的拆分后,再解析出各字段的值, 可能就无法再引发 DataReceived 事件。导致缓冲区堆积大量数据也会导致数据丢失等问题.
    FreeWong
        17
    FreeWong  
       Jun 12, 2025
    async ,await 在这里是不需要的
    hulalahei
        18
    hulalahei  
    OP
       Jun 12, 2025
    @hahiru 😀老哥的这个方法好像是可以的,一下午还没闪退。晚上还没闪退的话,200 红包就给你啦。红包咋给你比较方便。
    hulalahei
        19
    hulalahei  
    OP
       Jun 12, 2025
    ysc3839
        20
    ysc3839  
       Jun 12, 2025 via Android
    不懂 C#,但是 ReadAsync 不是直到有数据才返回吗?那直接循环 read 就完事了啊,为啥还要加事件?
    hahiru
        21
    hahiru  
       Jun 13, 2025
    @hulalahei #18 没事不用了。
    hulalahei
        22
    hulalahei  
    OP
       Jun 14, 2025
    @ysc3839 我一开始就是循环 read 的,返回的数据速度上升之后,大概十分钟就会出现同样的问题。可能和设备有一定的关系,这个设备十几年前买的。但是新设备同样有这个问题,只是出现的频率低一点。
    kevin100702
        23
    kevin100702  
       Jun 26, 2025 via Android
    system.io.pipeline 才是终极解决方案
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Solana   ·   2674 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 55ms · UTC 15:59 · PVG 23:59 · LAX 08:59 · JFK 11:59
    ♥ Do have faith in what you're doing.