如何解决.NET SerialPort控件效率低下的问题?

AlexYzhov 2016-02-16 12:14:13
以同样的频率从下位机接收数据,利用QT和.NET写的程序性能竟然差了10多倍。。





.NET程序是自己用C#写的,QT的程序是网上下载的串口助手。。具体事件如下
private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)  //接收中断事件
{
System.Threading.Thread.Sleep(100); //延时100ms,等待缓冲区被写入数据
//this.Invoke是跨线程访问UI的方法
this.Invoke((EventHandler)(delegate
{
//检验数据格式
if (radioButton_disHEX.Checked)
{
isDisHEX = true;
isDisASCII = false;
}
else if (radioButton_disASCII.Checked)
{
isDisHEX = false;
isDisASCII = true;
}

if (isDisHEX == false || isDisASCII == true) //以ASCII方式处理数据
{
richTextBox_Receive.SelectionColor = Color.Red;
richTextBox_Receive.AppendText( "<Recv>: " + serialPort.ReadLine() + "\r\n");//以ASCII方式显示数据
}
else //以HEX方式处理数据
{
Byte[] recvStr = new Byte[serialPort.BytesToRead]; //recvStr用于接收字节数据
float[] recvF = new float[serialPort.BytesToRead];
serialPort.Read(recvStr, 0, recvStr.Length);
string recvStrText = null; //声明数组存储接收数据
for (int i = 0; i < recvStr.Length - 1; i++)
{
recvF[i] = Convert.ToSingle(recvStr[i]);
//recvStrText += ("0x" + recvStr[i].ToString("X2") + " ");
}
for (int i = 0; i < recvStr.Length - 1; i++)
{
recvStrText += (recvF[i].ToString() + " ");
}
richTextBox_Receive.SelectionColor = Color.Red;
richTextBox_Receive.AppendText( "<Recv>: " + recvStrText + "\r\n" );
}
serialPort.DiscardInBuffer();//清空缓冲区
}
));
}


网上搜罗到了若干关于.NET串口性能低下的讨论:
http://bbs.csdn.net/topics/370185093
http://www.amobbs.com/thread-5481189-2-1.html
大致的结论是.NET自带的这个控件本身有问题。。

我的目的是想用C#写一个上位机调试参数,对数据的实时性有一定的要求。
现在发现数据收发频率稍微高一点,CPU占用率就异常的高。想改善这一现状,有什么好的方法吗?
用封装好WinAPI的类库(比如JustinIO,但似乎功能有问题?)?还是用第三方串口控件(QT?)?多语言混合编程(py)?还是继续改进SP类的参数(似乎没啥用)?
而且有没有方法用C#调用QT的qtserialport.dll完成串口部分的收发?
...全文
1192 19 打赏 收藏 转发到动态 举报
AI 作业
写回复
用AI写文章
19 条回复
切换为时间正序
请发表友善的回复…
发表回复
ZTLDS李明 2019-04-17
  • 打赏
  • 举报
回复
第一:延时要不得,c#接受结束有很多方法,如结束符之类的,你为什么要等待100毫秒?
第二:你只管接受,接收到之后开启个线程把数据丢出去处理
你学的什么面对对象啊,一大串数据处理写在事件里,开线程出去啊
wizzly 2016-02-16
  • 打赏
  • 举报
回复
你用封装好的串口控件,里面还有100ms的等待,然后委托,回调一堆子代码,时间肯长。要写高效串口通讯,还是直接操作IO,自己动手写。
xian_wwq 2016-02-16
  • 打赏
  • 举报
回复
撇开SerialPort本身效率低的问题不谈 lz的处理方式有几处是可以优化的 1.System.Threading.Thread.Sleep(100);可以根据真实场景进行调整 2.数据接收和数据解析处理建议分离,数据接收后,放入容器中,操作立即返回。 后续解析及显示可以使用单独线程,也可以使用.net提供的线程池 3.lz看下string和stringbuilder的区别,大量使用string 的+操作对性能影响非常大的
七步777 2016-02-16
  • 打赏
  • 举报
回复
哦 看错了,原来是读。CPU占用率高,应该是程序带来的。 1.PC的串口接受缓冲区,好像是2K吧,你可以查一下,如果是来一个字节读一个,和来1000个读一次,效率不一样的。 你可以将延迟再加长一点,但要保证接受数据没有溢出。CPU占用率会降一点 2.C#使用委托,反射的效率非常的低。你可以把这些去掉,先不在界面显示,直接打印出来。
cs1438250 2016-02-16
  • 打赏
  • 举报
回复
串口调试助手,只是个助手。工程里不能像他那么用串口。 打开了 一直占着不行,工程里发送时候打开,接收完就关闭串口。 一直开着,太占资源。
七步777 2016-02-16
  • 打赏
  • 举报
回复
System.Threading.Thread.Sleep(100); 你首先就延时100ms了,1秒你才写10次。当然慢了。 正确的做法是检查写缓冲区是否还有空间,有往里面继续写。
bdmh 2016-02-16
  • 打赏
  • 举报
回复
你自己都延时了Sleep(100),能不慢吗
  • 打赏
  • 举报
回复
你可以这样想一个最简单的问题:本来你担心性能问题,但是有人让你使用 Sleep,这是什么居心? 自己现有一个最基本的判断——绝对不可能写Sleep语句,这样把主要立场就走对了,那么“鬼”就少开始越来越少了。
  • 打赏
  • 举报
回复
引用 13 楼 AlexYzhov 的回复:
大概明白了,是不是因为sleep会挂起线程阻塞计算?
阻塞你正常读取后续数据。
本拉灯 2016-02-16
  • 打赏
  • 举报
回复
引用 13 楼 AlexYzhov 的回复:
[quote=引用 12 楼 wyd1520 的回复:]


 System.Threading.Thread.Sleep(10);                       //延时50ms,等待缓冲区被写入数据
 
            this.Invoke((EventHandler)(delegate
            {
                string recvStr = serialPort.ReadLine();    //recvStr用于接收字节数据
 
                string[] temp = new string[11] { "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" };
                         temp = recvStr.Split('#');
 
                FrameAcquaint( temp, ref Statue);
 
                serialPort.DiscardInBuffer();                         //清空缓冲区
            }
            ));
这里的代码根本不应这样写, private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) //串口接收事件(等待重写) { 这里只能放不阻塞线程的代码 int len=serialPort. BytesToRead]; byte[] readbuf=new byte[len]; serialPort.Read(readbuf,0,len) buffer.Enqueue(readbuf) <---放到缓冲区里面去, } 然后自己再启另一线程从缓冲区里取数据接下来怎么干都成
大概明白了,是不是因为sleep会挂起线程阻塞计算?[/quote] Sleep就是阻塞呀,还有复杂的运算放在那方法里也会阻塞,所以要分开另一线程去处理就不会了。
AlexYzhov 2016-02-16
  • 打赏
  • 举报
回复
引用 14 楼 sp1234 的回复:
至于其次的一些东西,虽然你可能现在根本搞不明白,我还是简单说一下思路: 在 serialPort_DataReceived 过程中只要出现同步执行
                    Byte[] recvStr = new Byte[serialPort.BytesToRead];    //recvStr用于接收字节数据
                    int len = serialPort.Read(recvStr, 0, recvStr.Length);
的语句就行了,然后剩下的解析 recevStr 中 len 个长度的字节、业务处理(例如显示到界面上)都应该异步、子线程去执行。这样就及时释放了串口 I/O,可以立刻处理下一组消息,而不会阻塞你的底层设备。 并且上述异步处理中只有“界面到显示”部分才需要 Control.BeginInvoke 语句(注册到主线程去执行),而消息解析和其它数据处理部分不需要在主线程执行,既不阻塞串口I/O也不阻塞主线程渲染。 没有必要使用 float[ ] 时,不要滥用它。此时最多使用 int[ ] 就够了。 如果频繁(循环)使用字符串拼接,那么你应该考虑 StringBuilder 来连接字符内容,而不是滥用拼接字符串。 你的最后的“清空缓冲区”这里是多余的,而且可能也会造成数据丢失。好的应该是写的很少,多余写代码一定是不好的。
学习了,多谢指教
  • 打赏
  • 举报
回复
至于其次的一些东西,虽然你可能现在根本搞不明白,我还是简单说一下思路: 在 serialPort_DataReceived 过程中只要出现同步执行
                    Byte[] recvStr = new Byte[serialPort.BytesToRead];    //recvStr用于接收字节数据
                    int len = serialPort.Read(recvStr, 0, recvStr.Length);
的语句就行了,然后剩下的解析 recevStr 中 len 个长度的字节、业务处理(例如显示到界面上)都应该异步、子线程去执行。这样就及时释放了串口 I/O,可以立刻处理下一组消息,而不会阻塞你的底层设备。 并且上述异步处理中只有“界面到显示”部分才需要 Control.BeginInvoke 语句(注册到主线程去执行),而消息解析和其它数据处理部分不需要在主线程执行,既不阻塞串口I/O也不阻塞主线程渲染。 没有必要使用 float[ ] 时,不要滥用它。此时最多使用 int[ ] 就够了。 如果频繁(循环)使用字符串拼接,那么你应该考虑 StringBuilder 来连接字符内容,而不是滥用拼接字符串。 你的最后的“清空缓冲区”这里是多余的,而且可能也会造成数据丢失。好的应该是写的很少,多余写代码一定是不好的。
AlexYzhov 2016-02-16
  • 打赏
  • 举报
回复
引用 12 楼 wyd1520 的回复:


 System.Threading.Thread.Sleep(10);                       //延时50ms,等待缓冲区被写入数据
 
            this.Invoke((EventHandler)(delegate
            {
                string recvStr = serialPort.ReadLine();    //recvStr用于接收字节数据
 
                string[] temp = new string[11] { "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" };
                         temp = recvStr.Split('#');
 
                FrameAcquaint( temp, ref Statue);
 
                serialPort.DiscardInBuffer();                         //清空缓冲区
            }
            ));
这里的代码根本不应这样写, private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) //串口接收事件(等待重写) { 这里只能放不阻塞线程的代码 int len=serialPort. BytesToRead]; byte[] readbuf=new byte[len]; serialPort.Read(readbuf,0,len) buffer.Enqueue(readbuf) <---放到缓冲区里面去, } 然后自己再启另一线程从缓冲区里取数据接下来怎么干都成
大概明白了,是不是因为sleep会挂起线程阻塞计算?
本拉灯 2016-02-16
  • 打赏
  • 举报
回复


 System.Threading.Thread.Sleep(10);                       //延时50ms,等待缓冲区被写入数据
 
            this.Invoke((EventHandler)(delegate
            {
                string recvStr = serialPort.ReadLine();    //recvStr用于接收字节数据
 
                string[] temp = new string[11] { "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" };
                         temp = recvStr.Split('#');
 
                FrameAcquaint( temp, ref Statue);
 
                serialPort.DiscardInBuffer();                         //清空缓冲区
            }
            ));
这里的代码根本不应这样写, private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) //串口接收事件(等待重写) { 这里只能放不阻塞线程的代码 int len=serialPort. BytesToRead]; byte[] readbuf=new byte[len]; serialPort.Read(readbuf,0,len) buffer.Enqueue(readbuf) <---放到缓冲区里面去, } 然后自己再启另一线程从缓冲区里取数据接下来怎么干都成
AlexYzhov 2016-02-16
  • 打赏
  • 举报
回复
引用 9 楼 wyd1520 的回复:
无力土槽 SerialPort说:代码写的烂,怪我咯。。。
还请高人指点?

        private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)  //串口接收事件(等待重写)
        {
            System.Threading.Thread.Sleep(10);                       //延时50ms,等待缓冲区被写入数据

            string recvStr = serialPort.ReadLine();    //recvStr用于接收字节数据

            string[] temp = new string[11] { "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" };

            temp = recvStr.Split('#');

            FrameAcquaint(temp, ref Statue);

            serialPort.DiscardInBuffer();                         //清空缓冲区
            }

        private void serialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)  //串口接收事件(等待重写)
        {
            System.Threading.Thread.Sleep(10);                       //延时50ms,等待缓冲区被写入数据

            this.Invoke((EventHandler)(delegate
            {
                string recvStr = serialPort.ReadLine();    //recvStr用于接收字节数据

                string[] temp = new string[11] { "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0" };
                         temp = recvStr.Split('#');

                FrameAcquaint( temp, ref Statue);

                serialPort.DiscardInBuffer();                         //清空缓冲区
            }
            ));
      }

            //设置超时读取时间
            serialPort.ReadTimeout = -1;

            //缓冲区有数据时(即触发接收时间)
            serialPort.ReceivedBytesThreshold = 100;

            serialPort.ReadBufferSize = 4096;
这样写的委托和非委托方式性能在我PC上没有多少差距,FrameAcquaint只是10个数据的赋值操作。而且SP类性能低下似乎在网上有很多讨论,不是一家之言。我主要的问题是,有没有不通过SP类写串口的方法?除了用WinAPI类库,可不可以在.NET下用QT的dll写?或者有没有其他的高性能串口库?
  • 打赏
  • 举报
回复
首先无论如何,那些什么“延迟、Sleep”的做法,本身就是瞎掰的,I/O 操作性能丧失当然是这种鬼弄出来的。假设你根本不能保证数据处理的准确(例如你胡乱在并发程序中使用共享变量,造成了数据状态混乱),那么也不要听信一些谣言来是用什么 Sleep。这个延迟时间“配置为”多长时间才能正确?配置1个小时好不好?显然即使设置为0也是多余的,你应该实际的问题。 想写好程序的第一步,一定要删除 Sleep 语句!
本拉灯 2016-02-16
  • 打赏
  • 举报
回复
无力土槽 SerialPort说:代码写的烂,怪我咯。。。
AlexYzhov 2016-02-16
  • 打赏
  • 举报
回复
引用 5 楼 cs1438250 的回复:
串口调试助手,只是个助手。工程里不能像他那么用串口。 打开了 一直占着不行,工程里发送时候打开,接收完就关闭串口。 一直开着,太占资源。
我想做的是个在线调试器,目的就是不断地回传下位机数据到PC上,方便侦测和调参 绘制界面的资源占用其实不高,如果数据不走串口,自行生成随机数,然后再刷控件的话资源占用率也只有3%左右 但是一旦数据通过串口传输,资源占用率就会多10倍
AlexYzhov 2016-02-16
  • 打赏
  • 举报
回复
现在的问题不是读取数据的速度不够,是CPU资源占用过多,其实我的程序是另一个,但是方法和这个串口助手大同小异。。以这个为例子是为了控制变量,以相同的频率接收数据。 sleep和readbuffer我试着调试过,收发过程也尽力优化了一些,但还是换汤不换药,对性能只有不超过5%资源占用率的影响。在我应用的程序上资源占用还是25%左右。和QT一共只有个位数的占用来说性能差距还是太大了。

111,098

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • AIGC Browser
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧