V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hulalahei
V2EX  ›  C#

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

  •  
  •   hulalahei · 49 天前 · 1845 次点击
    这是一个创建于 49 天前的主题,其中的信息可能已经有所发展或是发生改变。

    开发框架是 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 条回复    2025-06-26 01:27:23 +08:00
    muyiluop
        1
    muyiluop  
       49 天前
    int numRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length);
    这里为啥要异步读呢
    luojianxhlxt
        2
    luojianxhlxt  
       49 天前
    我现在都是用 TouchSocket

    要不你试试?

    https://touchsocket.net/docs/current/serialportclient
    hulalahei
        3
    hulalahei  
    OP
       49 天前
    @muyiluop 我之前也尝试过直接 read ,可以正常读数,但是一会读数就卡死了,_serialPort.BytesToRead 会不断上升。正常的数据是 5 个字节的,有的时候会突然一下返回几百个字节。
    Jscroop
        4
    Jscroop  
       49 天前
    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  
       49 天前
    @hulalahei #3 如果你是连续不断的接收数据的话,可以不管每次读到多少,直接丢给处理方法去处理,这里只管读。你的处理方法再去按 5 个字节或者你的数据结果去处理。返回几百个是因为你的 buffer 长度就是 512 。串口的数据他是数据流的概念,不像 udp 那种数据包的概念,发送端发了一包,接收端就接收 1 包
    hahiru
        6
    hahiru  
       49 天前   ❤️ 1
    DataReceived 事件触发的速度超过了 OnSerialDataReceived 方法中 ReadAsync 完成的速度。导致前一个 ReadAsync 还没结束下一个 DataReceived 事件又触发了,从而尝试在同一个 BaseStream 上发起第二次 ReadAsync 。
    简单修复就是加锁,遇到竞争就丢弃此次事件,等待下一次。
    GeruzoniAnsasu
        7
    GeruzoniAnsasu  
       49 天前
    根本原因是缓冲区和吞吐量太小,导致同步读会堆积数据,异步读会导致回调没完成就重入了

    为什么每次回调不直接读 BytesToRead 个字节?
    695975931
        8
    695975931  
       49 天前
    有个博主对 c# 的 debug 很有经验,你可以加他看看。他的公众号:一线码农聊技术。v:aXhjaHVhbmc=
    hulalahei
        9
    hulalahei  
    OP
       49 天前
    @GeruzoniAnsasu 回调直接读 BytesToRead 个字节我也试过,还是会报同样的错。
    hulalahei
        10
    hulalahei  
    OP
       49 天前
    @GeruzoniAnsasu 还有个问题,就是同步读数据堆积了,无法获取到实时的数据。
    hifeng
        11
    hifeng  
       49 天前
    类里面建个队列,ConcurrentQueue<byte[]> datas =new ConcurrentQueue<byte[]>(2000)
    回调直接读 BytesToRead 到 buff,
    添加到队列 datas.Enqueue(buff) 就返回;
    另外一个线程去处理 datas;
    hulalahei
        12
    hulalahei  
    OP
       49 天前
    @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
       49 天前
    @hifeng 我试试
    hulalahei
        14
    hulalahei  
    OP
       49 天前
    @hahiru 好的,我试试。
    andykuen959595
        15
    andykuen959595  
       49 天前
    @695975931 #8 哈哈哈 大佬都发朋友圈了
    FreeWong
        16
    FreeWong  
       49 天前
    DataReceived 事件在同一时间只能引发一个,如果你的设备发送的数据过快,而你在 DataReceived 事件中去做协议完整性的拆分后,再解析出各字段的值, 可能就无法再引发 DataReceived 事件。导致缓冲区堆积大量数据也会导致数据丢失等问题.
    FreeWong
        17
    FreeWong  
       49 天前
    async ,await 在这里是不需要的
    hulalahei
        18
    hulalahei  
    OP
       49 天前
    @hahiru 😀老哥的这个方法好像是可以的,一下午还没闪退。晚上还没闪退的话,200 红包就给你啦。红包咋给你比较方便。
    hulalahei
        19
    hulalahei  
    OP
       49 天前
    ysc3839
        20
    ysc3839  
       49 天前 via Android
    不懂 C#,但是 ReadAsync 不是直到有数据才返回吗?那直接循环 read 就完事了啊,为啥还要加事件?
    hahiru
        21
    hahiru  
       49 天前
    @hulalahei #18 没事不用了。
    hulalahei
        22
    hulalahei  
    OP
       47 天前
    @ysc3839 我一开始就是循环 read 的,返回的数据速度上升之后,大概十分钟就会出现同样的问题。可能和设备有一定的关系,这个设备十几年前买的。但是新设备同样有这个问题,只是出现的频率低一点。
    kevin100702
        23
    kevin100702  
       36 天前 via Android
    system.io.pipeline 才是终极解决方案
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   986 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 22:20 · PVG 06:20 · LAX 15:20 · JFK 18:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.