_C#_串口助手_字符串拼接缺失问题(未知原理)
最近使用WPF开发串口助手时,遇到一个很奇怪的问题,无论是主线程、异步还是多线程,当串口接收速度达到0.016s一次以上,就会发生字符串缺失问题并且很卡。而0.016s就一切如常,仿佛0.015s与0.016s是天堑之隔。
同一份代码放在多线程(主线程或异步)环境中:
由于是在测试,尝试过用索引下标的方式遍历数组
其中allData的定义为
List<string> allData = new List<string>();if (allData.Count > 0){RecvBuffer.Clear();StringBuilder sb = new StringBuilder();sb.Append(DateTime.Now);sb.Append(">>");Application.Current.Dispatcher.Invoke(() =>{for (int i = 0; i < allData.Count; i++){RecvBuffer.Append(sb);RecvBuffer.Append(allData[i]);}TextEditor?.AppendText(RecvBuffer.ToString());});}
0.016s一次接收:
日期可以正常打印下来,而且打印的速度很快,整体显示很流畅
0.015s一次接收:
虽然串口发送脚本的速度只提高了一点点,但是日期时间几乎不打印,只有零星几个有,而且整个显示非常卡
按理来说这两个速度差别应该不大,不至于产生这种差别,后来又相继测试了多线程、异步、主线程的全速打印(直接放进while死循环里,拟定的固定字符串) ,但都可以正常显示日期时间。
于是只能怀疑到字符串本身的问题了,由于此前本人是学C/C++的,对C#的字符串原理还不甚了解,大概知道C#的字符串中的“\0”并非作为字符串的结尾。但此时的打印结果却让我对C#的字符串拼接原理产生了深深的困惑。
根据前面的全速打印等测试情况,如果把硬编码的字符串或者数字塞进StringBuilder是没有问题的,所以想知道是不是字符串本身的问题,于是我把allData里的数据先转为数字,再直接拼接字符串:
if (allData.Count > 0){RecvBuffer.Clear();StringBuilder sb = new StringBuilder();sb.Append(DateTime.Now);sb.Append(">>");Application.Current.Dispatcher.Invoke(() =>{for (int i = 0; i < allData.Count; i++){RecvBuffer.Append(sb);var value = float.TryParse(allData[i], out var val);RecvBuffer.Append($"{val}\n");}TextEditor?.AppendText(RecvBuffer.ToString());});}虽然变得极其卡,但可以正常显示日期时间了
然而把字符串中的'\0'去掉之后并没有达到我的预期,依旧会很卡,且没有日期时间
查了许多资料,也问了AI很长时间,前前后后花了好几天,最终未能揭开其中的原理
不知道是队列的原因还是SerialPort中Read与ReadExisting的区别,或是字符串与字节数组的原因,亦或是多次接收的数据变为合并为一个数据,总之经过了下面变换,可用了,而且打印效率肉眼可见地提升,不再卡顿了。
// 原代码:// 在OnDataReceived中private void OnDataReceived(object? sender, SerialDataReceivedEventArgs e){if (sender is SerialPort sp){//ReceiveBytes += sp.BytesToRead;//++ReceiveNum;string data = sp.ReadExisting();_receiveQueue.Enqueue(data); // 将接收到的数据放入接收队列//_queueSemaphore.Release(); // 信号量释放,通知有新数据_resetEvent.Set(); // 通知读取任务开始处理数据}}// 读取任务private void ReadTask(){while (_isRunning){_resetEvent.WaitOne(); // 等待数据接收事件if (!_isRunning)break;List<string> allData = new List<string>();while (true){if (!(_receiveQueue.TryDequeue(out var data)))break;allData.Add(data);if (allData.Count > 10240)break;Thread.Sleep(10); // 适当休眠,避免过度占用CPU}if (allData.Count > 0){Application.Current.Dispatcher.Invoke(() =>{foreach (var item in allData){TextEditor?.AppendText($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}]>> {item}");}});}}}// 改进后:private void OnDataReceived(object? sender, SerialDataReceivedEventArgs e){if (sender is SerialPort sp){//ReceiveBytes += sp.BytesToRead;//++ReceiveNum;//string data = sp.ReadExisting();//_receiveQueue.Enqueue(data); // 将接收到的数据放入接收队列//_queueSemaphore.Release(); // 信号量释放,通知有新数据_resetEvent.Set(); // 通知读取任务开始处理数据}}public async void ReadTask(){const int MaxDataSize = 512;const int BatchSize = 1024; // 批量读取大小var allData = new List<byte>();while (true){_resetEvent.WaitOne(); // 等待数据接收事件try{while (allData.Count < MaxDataSize && _serialPort.BytesToRead > 0){var bytesToRead = Math.Min(_serialPort.BytesToRead, BatchSize);var buffer = new byte[bytesToRead];_serialPort.Read(buffer, 0, bytesToRead);allData.AddRange(buffer);if (allData.Count >= MaxDataSize) break;await Task.Delay(10); // 防止CPU占用过高,使用异步延迟}}catch{// 处理异常allData.Clear();continue;}if (allData.Count > 0){await LogMessageAsync(_serialPort.Encoding.GetString(allData.ToArray()), DateTime.Now.ToString());allData.Clear(); // 清空数据列表}}}private async Task LogMessageAsync(string inputData, string timestamp){// 使用StringBuilder提高效率var sb = new StringBuilder();sb.Append($"[{timestamp}] ");// 使用正则表达式替换换行符,并添加时间戳var logEntry = Regex.Replace(inputData, @"(\r\n|\r|\n)", $"\r\n{timestamp}>> ");sb.Append(logEntry);// 异步更新UIawait Application.Current.Dispatcher.InvokeAsync(() =>{TextEditor?.AppendText(sb.ToString());});}出于时间考量不再深究,于此留下痕迹。
2024.11.28:
淦!用这个可以流畅打印的函数试了之后发现一个不对劲的地方,那就是接收字节数过大,测了一下,发现Python脚本发送数据的真实速度是16359.5次/s,即0.00006s发送一次数据
千算万算没想到是python脚本的问题,不过这倒也解决了“0.015s”与0.016s一次差距悬殊的原因,并且也找到了能高性能显示的方案。真淦
下面是罪魁祸首
import asyncio import math import randomimport serial import serial_asyncioclass VirtualSerialServer(asyncio.Protocol):def __init__(self, baudrate=115200, interval=0.1, frequency=1.0, amplitude=1.0, offset=0.0):self.transport = Noneself.baudrate = baudrateself.interval = intervalself.frequency = frequency # 正弦波频率(Hz)self.amplitude = amplitude # 正弦波振幅self.offset = offset # 正弦波偏移量self.time = 0.0 # 当前时间def connection_made(self, transport):self.transport = transportprint(f'Virtual Serial Port connected with baudrate {self.baudrate}')asyncio.create_task(self.send_data_periodically())# def data_received(self, data):# print(f'Received: {data.decode()}')# # 回显接收到的数据# self.transport.write(data)async def send_data_periodically(self):value = 0while True:# 生成100以内的随机整数# value = random.randint(0, 100)# 将整数转换为字符串格式value += 1data_to_send = f'{value}\n'.encode('utf-8')self.transport.write(data_to_send)# 打印数据# print(f'Sent: {value}')await asyncio.sleep(self.interval)async def main():# 固定波特率和时间间隔baudrate = 115200interval = 0.015 # 发送间隔(秒)# 使用实际的虚拟串口号,例如 'COM15'port = 'COM15'loop = asyncio.get_running_loop()try:server = await serial_asyncio.create_serial_connection(loop, lambda: VirtualSerialServer(baudrate, interval), url=port, baudrate=baudrate)except serial.serialutil.SerialException as e:print(f"Error opening serial port {port}: {e}")returnawait asyncio.Event().wait() # 保持程序运行if __name__ == '__main__':try:asyncio.run(main())except KeyboardInterrupt:print('程序已终止。')经过一些测试,发现在异步情况下,Python的定时执行容易出现很奇葩的问题,在我的例子中0.015s就是那个极限,0.016s及其以上却是正常的。
下面是经过改正且可以正常使用的代码
import time import serialclass VirtualSerialServer:def __init__(self, interval=0.015):self.interval = intervalself.last_send_time = None # 记录上次发送时间self.value = 0 # 初始化发送的数据值def start(self, port, baudrate):try:with serial.Serial(port, baudrate) as ser:print(f'Virtual Serial Port connected with interval {self.interval}')while True:current_time = time.perf_counter()if self.last_send_time is not None:actual_interval = current_time - self.last_send_timeprint(f'Sent: {self.value}, Actual interval: {actual_interval:.6f} seconds') # 打印实际间隔# 发送数据self.value += 1data_to_send = f'{self.value}\n'.encode('utf-8')ser.write(data_to_send)# 更新最后一次发送时间self.last_send_time = current_time# 等待直到下一个发送时间点next_send_time = self.last_send_time + self.intervalwhile time.perf_counter() < next_send_time:pass # 空循环等待直到达到下一个发送时间点except serial.SerialException as e:print(f"Error opening serial port {port}: {e}")if __name__ == '__main__':interval = 0.012 # 发送间隔(秒)port = 'COM15' # 使用实际的虚拟串口号baudrate = 115200 # 固定波特率server = VirtualSerialServer(interval=interval)try:server.start(port, baudrate)except KeyboardInterrupt:print('程序已终止。')
相关文章:
_C#_串口助手_字符串拼接缺失问题(未知原理)
最近使用WPF开发串口助手时,遇到一个很奇怪的问题,无论是主线程、异步还是多线程,当串口接收速度达到0.016s一次以上,就会发生字符串缺失问题并且很卡。而0.016s就一切如常,仿佛0.015s与0.016s是天堑之隔。 同一份代码…...
浅析大数据时代下的网络安全
一、大数据时代下网络安全的现状 在全球化进程不断深入发展的情况下,互联网行业发展速度也更加迅猛,人们对网络信息的需求量不断增加,所以目前已经进入了大数据时代。 随着计算机技术的不断发展,我国互联网网络规模、网民数量、…...
Mysql数据库基础篇笔记
目录 sql语句 DDL——数据库定义语言(定义库,表,字段) 数据库操作: 表操作: DML 增删改语句 DQL 语法编写顺序: 条件查询 DCL 用户管理: 权限管理: 函数 常见字符串内置函…...
rabbitmq原理及命令
目录 一、RabbitMQ原理1、交换机(Exchange)fanoutdirecttopicheaders(很少用到) 2、队列Queue3、Virtual Hosts4、基础对象 二、RabbitMQ的一些基本操作:1、用户管理2、用户角色3、vhost4、开启web管理接口5、批量删除队列 一、Ra…...
React进阶面试题(四)
React 的 reconciliation(协调)算法 Reconciliation是React的diff算法,用于比较更新前后的虚拟DOM树差异,从而使用最小的代价将原始DOM按照新的状态、属性进行更新。其目的是找出两棵树的差异,原生方式直接比较复杂度…...
24/12/1 算法笔记<强化学习> 创建Maze交互
我们今天制作一个栅格的游戏。 我们直接上代码教学。 1.载入库和查找相应的函数版本 import numpy as np import time import sysif sys.version_info.major 2:import Tkinter as tk else:import tkinter as tk 2.设置长宽和单元格大小 UNIT 40 MAZE_H 4 MAZE_W 4 3.初始…...
Linux驱动开发(10):I2C子系统–mpu6050驱动实验
本章我们以板载MPU6050为例讲解i2c驱动程序的编写,本章主要分为五部分内容。 第一部分,i2c基本知识,回忆i2c物理总线和基本通信协议。 第二部分,linux下的i2c驱动框架。 第三部分,i2c总线驱动代码拆解。 第四部分&a…...
《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》
《装甲车内气体检测“神器”:上海松柏 K-5S 电化学传感器模组详解》 一、引言二、K-5S 电化学传感器模组概述(一)产品简介(二)产品特点(三)产品适用场景 三、电化学传感器原理及优点(一…...
如何将多个JS文件打包成一个JS文件?
文章目录 前言SDK 打包安装 webpack创建 webpack.config.js编译命令行遇到的坑点前言 上一篇已经记录了如何开发一个小游戏聚合SDK,既然是SDK,最终都是给外部人员使用的。调研了一下市面上的前端SDK,最终都是编译成一个 js 文件。我猜理由大概是 js 文件之间的调用都是需要…...
100个python经典面试题详解(新版)
应老粉要求,每晚加餐一个最新面试题 包括Python面试中常见的问题,涵盖列表、元组、字符串插值、比较操作符、装饰器、类与对象、函数调用方式、数据结构操作、序列化、数据处理函数等多个方面。 旨在帮助数据科学家和软件工程师准备面试或提升Python技能。 7、Python面试题…...
C#初阶概念理解
梳理了一些本人在学习C#时的一些生疏点,同时也加深自己的印象。 堆&栈 堆用来存储程序运行时产生的变量,当程序结束时释放; 栈用来存储程序运行时,调用方法产生的临时变量,方法运行完成后就会释放…...
node.js基础学习-url模块-url地址处理(二)
前言 前面我们创建了一个HTTP服务器,如果只是简单的http://localhost:3000/about这种链接我们是可以处理的,但是实际运用中一般链接都会带参数,这样的话如果我们只是简单的判断链接来分配数据,就会报404找不到链接。为了解决这个问…...
算法与数据结构(1)
一:数据结构概论 数据结构分为初阶数据结构(主要由C语言实现)和高阶数据结构(由C实现) 初阶数据结构当中,我们会学到顺序表、链表、栈和队列、二叉树、常见排序算法等内容。 高阶数据结构当中࿰…...
FTP介绍与配置
前言: FTP是用来传送文件的协议。使用FTP实现远程文件传输的同时,还可以保证数据传输的可靠性和高效性。 介绍 FTP的应用 在企业网络中部署一台FTP服务器,将网络设备配置为FTP客户端,则可以使用FTP来备份或更新VRP文件和配置文件…...
SQL面试题——抖音SQL面试题 最近一笔有效订单
最近一笔有效订单 题目背景如下,现有订单表order,包含订单ID,订单时间,下单用户,当前订单是否有效 +---------+----------------------+----------+-----------+ | ord_id | ord_time | user_id | is_valid | +---------+----------------------+--------…...
【线程】Java多线程代码案例(1)
【线程】Java多线程代码案例(1) 一、“单例模式” 的实现1.1“饿汉模式”1.2 “懒汉模式”1.3 线程安全问题 二、“阻塞队列”的实现2.1阻塞队列2.2生产者消费者模型2.3 阻塞队列的实现2.4 再谈生产者消费者模型 一、“单例模式” 的实现 “单例模式”即…...
go使用mysql实现增删改查操作
1、安装MySQL驱动 go get -u github.com/go-sql-driver/mysql2、go连接MySQL import ("database/sql""log"_ "github.com/go-sql-driver/mysql" // 导入 mysql 驱动 )type Users struct {ID intName stringEmail string }var db *sql.DBfu…...
【Rust】unsafe rust入门
这篇文章简单介绍下unsafe rust的几个要点 1. 解引用裸指针 裸指针其实就是C或者说C的指针,与C的指针不同的是,Rust的裸指针还是要分为可变和不可变,*const T 和 *mut T: 基于引用创建裸指针 let mut num 5;let r1 &num …...
dpwwn02靶场
靶机下载地址:https://download.vulnhub.com/dpwwn/dpwwn-02.zip 信息收集 ip add 查看kali Linux虚拟机的IP为:10.10.10.128 https://vulnhub.com/entry/dpwwn-2,343/中查看靶机的信息,IP固定为10.10.10.10 所以kali Linux添加仅主机网卡…...
K8S疑难概念理解——Pod,应该以哪种Kind来部署应用,为什么不直接Pod这种kind?
文章目录 一、Pod概念深度理解,为什么一般不直接以kindPod资源类型来部署应用?二、究竟应该以哪种资源类型来部署应用 一、Pod概念深度理解,为什么一般不直接以kindPod资源类型来部署应用? Pod是Kubernetes中的最小部署单元,可以包含一个或…...
JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
Oracle查询表空间大小
1 查询数据库中所有的表空间以及表空间所占空间的大小 SELECTtablespace_name,sum( bytes ) / 1024 / 1024 FROMdba_data_files GROUP BYtablespace_name; 2 Oracle查询表空间大小及每个表所占空间的大小 SELECTtablespace_name,file_id,file_name,round( bytes / ( 1024 …...
如何在看板中有效管理突发紧急任务
在看板中有效管理突发紧急任务需要:设立专门的紧急任务通道、重新调整任务优先级、保持适度的WIP(Work-in-Progress)弹性、优化任务处理流程、提高团队应对突发情况的敏捷性。其中,设立专门的紧急任务通道尤为重要,这能…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
力扣-35.搜索插入位置
题目描述 给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...
【MATLAB代码】基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),附源代码|订阅专栏后可直接查看
文章所述的代码实现了基于最大相关熵准则(MCC)的三维鲁棒卡尔曼滤波算法(MCC-KF),针对传感器观测数据中存在的脉冲型异常噪声问题,通过非线性加权机制提升滤波器的抗干扰能力。代码通过对比传统KF与MCC-KF在含异常值场景下的表现,验证了后者在状态估计鲁棒性方面的显著优…...
【 java 虚拟机知识 第一篇 】
目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...




