当然有!你提供的这段 C# WinForms 代码功能非常完整且考虑周全,尤其是在 UI 交互、多线程处理和状态管理方面做得相当不错。
在此基础上,我们可以从性能、架构和代码可维护性三个角度进行优化,让这个应用变得更加出色。
核心性能优化(高优先级)
这两点改进能最显著地提升应用的流畅度和响应速度。
-
数据采集:从"轮询"改为"事件驱动"
问题:
你现在的后台任务 RefreshDataLoopAsync 是一个轮询循环,它以固定的 50ms 间隔主动去 GetDeviceData。这种方式的主要缺点是:
● 不精确:如果设备回报率高于 20Hz (50ms),你会丢失数据点。如果低于 20Hz,则会进行不必要的空查询,浪费 CPU 资源。
● 非实时:数据从产生到被你读取,最多有 50ms 的延迟。
改进方案:
利用 SDK 提供的 BWT901BLE_OnRecord 事件。这个事件在传感器主动上报数据时触发,是最理想的数据来源。
你可以构建一个经典的"生产者-消费者"模型:
-
生产者 (OnRecord 事件):在事件回调中,将传感器数据放入一个线程安全的队列(如 System.Collections.Concurrent.BlockingCollection)。
-
消费者 (后台任务):后台任务不再使用 Task.Delay,而是持续从队列中取出数据,然后存入你的 _accelerationBuffer 环形缓冲。
这样做的好处:
● 不错过任何数据:只要数据上报,就能被捕获。
● 效率极高:没有数据时,后台任务处于等待状态,不消耗 CPU。
● 实时性最佳:数据一到就被处理。
-
图表绘制:用"滑动窗口"代替"完全重绘"
问题:
UpdateChartData 方法每次都调用 Points.Clear() 来清空所有数据点,然后重新添加。即使只显示 100 个点,这种操作也会导致图表闪烁,并且在数据更新频繁时消耗大量资源。
改进方案:
实现一个"滑动窗口"式的更新。
-
向图表系列(Series)中添加新数据点。
-
检查当前点的数量是否超过 MaxDataPoints。
-
如果超过,就移除最老的一个数据点 (Points.RemoveAt(0))。
这样,图表每次只进行最小化的重绘,动画效果会非常平滑流畅。
C#
// 示例:滑动更新图表
private void AddDataPointToChart(AccelerationData data)
{
// ...
var seriesX = chart1.Series["AccX"];
seriesX.Points.AddXY(data.Timestamp.ToOADate(), data.AccX);
// 如果点数超过上限,移除最旧的点
if (seriesX.Points.Count > MaxDataPoints)
{
seriesX.Points.RemoveAt(0);
}
// ... 对 AccY 和 AccZ 执行相同操作
// 相应地调整X轴的视图范围
chart1.ChartAreas[0].AxisX.Minimum = seriesX.Points[0].XValue;
chart1.ChartAreas[0].AxisX.Maximum = data.Timestamp.ToOADate();
}
软件架构优化(提升可维护性)
-
类的职责分离(Refactoring)
问题:
Form2 这个类承担了太多的责任,它既是 UI 界面,又是蓝牙管理器,还是数据处理器和图表控制器。这使得这个文件非常庞大(超过1500行),难以维护和扩展。
改进方案:
将不同的功能模块拆分到独立的类中,遵循"单一职责原则"。
● DeviceService.cs: 专门负责蓝牙设备的扫描、连接、数据接收和指令发送。Form 通过调用它来管理设备,并通过事件来接收数据。
● ChartController.cs: 专门负责 chart1 的所有操作,如图表初始化、数据更新、样式设置等。
● DataRepository.cs: 专门负责 _accelerationBuffer 的管理,如数据存入、读取、清空、导出等。
这样做的好处:
● 代码清晰:Form2.cs 会变得非常简洁,主要负责响应用户操作,并将任务委托给其他类。
● 易于维护:修改蓝牙逻辑时,你只需要看 DeviceService.cs 文件。
● 可复用和测试:这些独立的业务类可以在其他项目中复用,并且可以单独进行单元测试。
功能与体验优化(锦上添花)
-
支持多设备连接与切换
问题:
当前应用在找到第一个符合条件的设备后就停止扫描并进行连接。如果周围有多个传感器,用户无法选择。
改进方案:
● 在 UI 上添加一个 ListBox 或 ComboBox。
● 扫描时,将所有找到的设备都添加到这个列表中。
● 允许用户从列表中选择一个或多个设备进行连接。
● 如果连接了多个设备,可以提供一个下拉框让用户切换图表所显示的数据源。
-
耗时操作异步化
问题:
exportButton_Click 中的数据导出是同步执行的。如果历史数据非常多,写入文件时会造成 UI 线程阻塞,导致程序"未响应"。
改进方案:
将文件写入的核心逻辑放到 Task.Run 中执行,并使用 async/await 来等待它完成,避免冻结界面。
C#
private async void exportButton_Click(object sender, EventArgs e)
{
// ... 获取文件路径等 ...
try
{
exportButton.Enabled = false; // 禁用按钮,防止重复点击
ShowStatusMessage("正在导出...", MessageType.Info);
await Task.Run(() => { // 将耗时的文件写入操作放在这里 File.WriteAllLines(filePath, dataLines); }); ShowStatusMessage("导出成功!", MessageType.Success);}
catch (Exception ex)
{
ShowErrorMessage("导出失败", ex);
}
finally
{
exportButton.Enabled = true; // 恢复按钮
}
}
总结
总的来说,你的代码基础非常好。如果你希望它能更上一层楼,我强烈建议你:
-
首要任务:将数据采集方式从轮询改为事件驱动,并优化图表的更新逻辑。这两项改动对性能的提升是立竿见影的。
-
长期考虑:进行代码重构,将功能拆分到不同的类中。这会让你的项目在未来更容易维护和添加新功能。
希望这些建议对你有所帮助!