Appearance
案例 A-02:ADC 曲线固定窗口刷新
这个案例最容易被误解成“实时曲线都应该固定 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 pointsUI 可能更适合每个完整帧直接替换当前曲线。
这两种方案不能同时写成同一个项目的固定策略。
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. 本案例最值得保留的经验
- 数据留存和 UI 窗口分开。 UI 截断不能决定正式历史是否存在。
- 连续流和完整帧是不同语义。 SlidingWindow 与 FrameReplace 二选一或明确组合规则。
- 后台 List 无限增长同样是问题。 不要只解决图表点数。
- 采样率不等于 UI 刷新率。 UI 可以限频而不丢正式数据。
- 配置字段要看语义层。
MaxChartPoints不自动等于设备采样点数。 - 性能优化必须有基线。 没测就不能宣布变快。