Skip to content

案例 A-02:ADC 曲线固定窗口刷新

内容类型:真实脱敏案例难度:进阶适合:处理 WPF 实时曲线、采集数据和长稳性能的开发者阅读时间:约 10 分钟前置:了解 WPF 数据绑定和图表基础

这个案例最容易被误解成“实时曲线都应该固定 5000 点”。真正应该保留的原则是:

正式数据、计算工作集和 UI 可视窗口是三个不同生命周期。显示多少点,不应自动决定保存多少数据。

1. 原始问题

持续采集 ADC 后,图表点数不断增长,UI 刷新越来越慢。

当时项目希望:

  • 屏幕只保留有限可视点数;
  • 正式采集/保存不因为图表窗口而丢失;
  • 点数上限可配置;
  • UI 不因长时间运行无限增长。

这首先是 Performance / Long-run + UI 问题,而不是“换图表控件”问题。

2. 先确认数据到底是哪一种

实时曲线常见至少两种语义。

Continuous Stream

例如连续采样:

text
... p1001 p1002 p1003 p1004 ...

UI 常适合使用滑动窗口:

text
只显示最近 N 点 / 最近 T 秒

Frame Snapshot

例如每轮设备返回一整帧 5000 点波形:

text
Frame 1 = 5000 points
Frame 2 = 5000 points

UI 可能更适合每个完整帧直接替换当前曲线。

这两种方案不能同时写成同一个项目的固定策略。

3. 本案例旧描述中的矛盾

历史版本同时出现了:

  • “每收到完整帧清空重绘”;
  • “始终显示最近 N 点,移除最旧点”;
  • “曲线平滑滚动”。

它们属于不同刷新语义。

V10 的正确做法是先根据当前数据模型确定一个:

text
DisplayMode:
FrameReplace | SlidingWindow

而不是先写 MaxChartPoints 再反推行为。

4. 三层数据生命周期

推荐至少分清:

text
Persistent Data
= 正式保存/追溯的数据

Working Set
= 当前计算、解析或临时缓存需要的数据

Display Window
= 当前 UI 为了可读性和性能显示的数据

Persistent Data

可以写文件/数据库/专用记录,不意味着必须长期全部驻留内存。

Working Set

按计算需要保留,例如当前帧、最近若干秒、滤波窗口。

Display Window

只服务界面,可进一步降采样、截断或替换。

因此旧版本的:

text
全部数据追加到 List<DataPoint>
→ UI TakeLast(N)

在短时案例中可能可用,但不能作为长稳通用设计。如果 List 永久增长,内存问题只是从图表控件转移到了后台集合。

5. 两种正确的显示策略

A. SlidingWindow

适合连续流:

text
new samples
→ append to bounded display buffer
→ remove/overwrite oldest
→ UI refresh at controlled rate

关键参数:

text
WindowSize / WindowDuration
UIRefreshRate
Downsample policy(如需要)

B. FrameReplace

适合完整帧:

text
collect frame
→ frame complete
→ replace displayed series

不需要把上一帧和下一帧拼成一个“最近 N 点”滑动窗口,除非业务就是这样定义。

6. UI 刷新频率和采样频率不是一回事

设备 10 kHz 采样,不代表 UI 也应该 10 kHz 刷新。

可以分别定义:

text
AcquisitionRate
ProcessingRate
PersistenceRate / Batch
UIRefreshRate

例如采样很快,但 UI 只每 50–200 ms 刷一次,正式数据仍完整保存。

实际数值由项目实测决定,不把某个刷新周期写成通用常数。

7. 配置项先判断是不是 DomainConstraint

MaxChartPoints 如果只控制显示窗口,通常是 Display/UI 配置。

如果它同时影响:

  • 正式保存;
  • 算法计算;
  • 外部接口;
  • 设备采样点数;

那它已经不是简单 UI 配置,应按 业务约束跨层传播 检查。

不要因为字段名一样,就默认四层语义是同一个“点数”。

8. 性能诊断先量化,不先换控件

出现卡顿时先记录:

text
采样率
每帧点数
UI refresh rate
当前可见点数
集合总长度
一次刷新耗时
Dispatcher 队列/调用频率
CPU
Working Set / GC

然后定位第一个主要热点。

可能的原因包括:

  • 无限增长集合;
  • 每个采样点都触发通知;
  • Dispatcher 过频;
  • 每帧分配大量新对象;
  • 图表全量重建;
  • 日志/保存和 UI 在同一线程;
  • 图表库在当前数据规模下不合适。

不能仅凭“WPF 曲线卡”直接得出“换 ScottPlot/LiveCharts/OxyPlot”。

对应专题:性能与长稳诊断

9. 验证建议

行为验证

根据实际 DisplayMode:

  • SlidingWindow:窗口到达上限后不继续增长,顺序正确;
  • FrameReplace:完整帧到达后只显示当前帧;
  • 配置改变后的生效时机符合定义。

数据完整性

验证:

  • UI 窗口变化不会裁剪正式持久化;
  • 保存数据的单位/时间/序列保持正确;
  • Stop 后 buffer/writer 正确结束。

性能

必须使用同一场景前后对比,例如:

text
Baseline:
60 min 后 Working Set 1.2 GB
UI p95 180 ms

After:
60 min 后 Working Set 430 MB
UI p95 28 ms

没有这种基线,不写“性能明显提升”“内存稳定”。

HardwarePending

真实设备的持续负载、驱动节奏和多通道竞争若尚未测试,明确保持 HardwarePending

10. 本案例最值得保留的经验

  1. 数据留存和 UI 窗口分开。 UI 截断不能决定正式历史是否存在。
  2. 连续流和完整帧是不同语义。 SlidingWindow 与 FrameReplace 二选一或明确组合规则。
  3. 后台 List 无限增长同样是问题。 不要只解决图表点数。
  4. 采样率不等于 UI 刷新率。 UI 可以限频而不丢正式数据。
  5. 配置字段要看语义层。 MaxChartPoints 不自动等于设备采样点数。
  6. 性能优化必须有基线。 没测就不能宣布变快。

相关方法

下一步

别来无恙 · C# 上位机 AI 实战站 · 从零到交付 · QQ 群:1016188499