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

50 天前
 hulalahei

开发框架是 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 红包感谢。

1853 次点击
所在节点    C#
23 条回复
muyiluop
50 天前
int numRead = await _serialPort.BaseStream.ReadAsync(buffer, 0, buffer.Length);
这里为啥要异步读呢
luojianxhlxt
50 天前
我现在都是用 TouchSocket

要不你试试?

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

为什么每次回调不直接读 BytesToRead 个字节?
695975931
50 天前
有个博主对 c# 的 debug 很有经验,你可以加他看看。他的公众号:一线码农聊技术。v:aXhjaHVhbmc=
hulalahei
50 天前
@GeruzoniAnsasu 回调直接读 BytesToRead 个字节我也试过,还是会报同样的错。
hulalahei
50 天前
@GeruzoniAnsasu 还有个问题,就是同步读数据堆积了,无法获取到实时的数据。
hifeng
50 天前
类里面建个队列,ConcurrentQueue<byte[]> datas =new ConcurrentQueue<byte[]>(2000)
回调直接读 BytesToRead 到 buff,
添加到队列 datas.Enqueue(buff) 就返回;
另外一个线程去处理 datas;
hulalahei
50 天前
@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
50 天前
@hifeng 我试试
hulalahei
50 天前
@hahiru 好的,我试试。
andykuen959595
50 天前
@695975931 #8 哈哈哈 大佬都发朋友圈了
FreeWong
50 天前
DataReceived 事件在同一时间只能引发一个,如果你的设备发送的数据过快,而你在 DataReceived 事件中去做协议完整性的拆分后,再解析出各字段的值, 可能就无法再引发 DataReceived 事件。导致缓冲区堆积大量数据也会导致数据丢失等问题.
FreeWong
50 天前
async ,await 在这里是不需要的
hulalahei
50 天前
@hahiru 😀老哥的这个方法好像是可以的,一下午还没闪退。晚上还没闪退的话,200 红包就给你啦。红包咋给你比较方便。
hulalahei
50 天前
ysc3839
50 天前
不懂 C#,但是 ReadAsync 不是直到有数据才返回吗?那直接循环 read 就完事了啊,为啥还要加事件?

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1138099

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX