绘制EKG图表时GUI冻结问题及批量快速绘制需求
问题描述
现有如下代码:
private void FormEKG_Load(object sender, EventArgs e){ InitializeChartEKG(); RunParser(); } private async void RunParser () { await System.Threading.Tasks.Task.Run(FillChartEKG); // 耗时的后台处理 CallBack(); } private async void CallBack() { DrawChartEKG(parsedEKGData, counter, loopToStopAt); } private void DrawChartEKG (List<AnesthParsedEKGData> parsedEKGData, int counter, int loopToStopAt) { for (int i = 0; i < parsedEKGData.Count; i++) { series1.Points.AddXY(parsedEKGData[i].TimeStamp, parsedEKGData[i].Point); chartEKG.ChartAreas[0].AxisX.Maximum = counter * 500; chartEKG.ChartAreas[0].AxisX.ScaleView.Scroll(chartEKG.ChartAreas[0].AxisX.Maximum - Convert.ToDouble(loopToStopAt)); } }
其中FillChartEKG执行流畅不会冻结GUI,但绘制图表时GUI会冻结约20秒。请问如何修改代码解决该问题?
顺带一提,若修改为如下代码则不会冻结:
private async void DrawChartEKG (List<AnesthParsedEKGData> parsedEKGData, int counter, int loopToStopAt) { await System.Threading.Tasks.Task.Run(() => { for (int i = 0; i < parsedEKGData.Count; i++) { Invoke(new Action(() => series1.Points.AddXY(parsedEKGData[i].TimeStamp, parsedEKGData[i].Point))); } }); chartEKG.ChartAreas[0].AxisX.Maximum = counter * 500; chartEKG.ChartAreas[0].AxisX.ScaleView.Scroll(chartEKG.ChartAreas[0].AxisX.Maximum - Convert.ToDouble(loopToStopAt)); }
但此时图表会逐点实时绘制(类似实时心电图),因测试案例时长约1小时,绘制需耗时超1小时。我希望能在约20秒内批量完成整个案例的图表绘制。
附:图表初始化代码如下:
private void InitializeChartEKG () { series1 = new Series { Color = Color.LawnGreen, IsVisibleInLegend = false, IsXValueIndexed = true, ChartType = SeriesChartType.FastLine }; chartEKG.Series.Clear(); chartEKG.Series.Add(series1); chartEKG.ChartAreas[0].AxisX.Title = "Time"; chartEKG.ChartAreas[0].AxisX.MajorTickMark.Enabled = true; chartEKG.ChartAreas[0].AxisX.MinorTickMark.Enabled = true; chartEKG.ChartAreas[0].AxisX.IsStartedFromZero = true; chartEKG.ChartAreas[0].CursorX.LineColor = Color.LawnGreen; chartEKG.ChartAreas[0].CursorY.LineColor = Color.LawnGreen; chartEKG.ChartAreas[0].AxisX.MajorGrid.Enabled = true; chartEKG.ChartAreas[0].AxisX.MajorGrid.Interval = 100; chartEKG.ChartAreas[0].AxisX.IsStartedFromZero = true; chartEKG.ChartAreas[0].AxisX.MajorTickMark.Enabled = true; chartEKG.ChartAreas[0].AxisX.Minimum = 0; chartEKG.ChartAreas[0].AxisY.Maximum = 1000; chartEKG.ChartAreas[0].AxisY.Minimum = -600; chartEKG.ChartAreas[0].AxisX.Interval = 10000; chartEKG.ChartAreas[0].AxisY.Title = "mV"; chartEKG.Series[0].XValueType = ChartValueType.DateTime; chartEKG.Series[0].YValueType = ChartValueType.Int32; chartEKG.ChartAreas[0].AxisX.IntervalType = DateTimeIntervalType.Auto; chartEKG.ChartAreas[0].AxisX.LabelStyle.IntervalType = DateTimeIntervalType.Auto; chartEKG.ChartAreas[0].AxisX.LabelStyle.Format = "HH:mm:ss tt"; chartArea = chartEKG.ChartAreas[0]; chartArea.AxisX.ScaleView.SizeType = DateTimeIntervalType.Auto; int position = 0; int blockSize = 10000; int size = blockSize; chartArea.AxisX.ScaleView.Zoom(position, size); chartEKG.ChartAreas[0].AxisX.ScaleView.Position = chartEKG.ChartAreas[0].AxisX.Maximum; chartArea.AxisX.ScaleView.SmallScrollSize = blockSize; chartArea.CursorX.AutoScroll = true; chartArea.AxisX.ScrollBar.BackColor = Color.LightGray; chartArea.AxisX.ScrollBar.ButtonColor = Color.LightSteelBlue; chartArea.AxisX.ScrollBar.LineColor = Color.DarkBlue; chartArea.AxisX.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll; chartArea.AxisX.ScrollBar.Size = 20; CultureInfo culture = new CultureInfo("en-US"); }
解决方案
核心思路是批量添加数据+暂停图表重绘,既避免GUI冻结,又保证绘制速度:
修改后的DrawChartEKG代码
private async void DrawChartEKG(List<AnesthParsedEKGData> parsedEKGData, int counter, int loopToStopAt) { // 确保后续UI操作在UI线程执行 await Task.Yield(); // 暂停图表布局和数据点更新,避免频繁重绘 chartEKG.SuspendLayout(); series1.Points.SuspendUpdates(); try { // 批量添加所有数据点 foreach (var data in parsedEKGData) { series1.Points.AddXY(data.TimeStamp, data.Point); } // 统一设置轴配置 chartEKG.ChartAreas[0].AxisX.Maximum = counter * 500; chartEKG.ChartAreas[0].AxisX.ScaleView.Scroll(chartEKG.ChartAreas[0].AxisX.Maximum - Convert.ToDouble(loopToStopAt)); } finally { // 恢复更新和布局,触发一次完整重绘 series1.Points.ResumeUpdates(); chartEKG.ResumeLayout(); } }
关键优化点说明
SuspendLayout()和SuspendUpdates():阻止控件在数据变更时立即重绘,直到调用对应的Resume方法,大幅减少重绘次数,这是提升速度的核心。- 避免逐点
Invoke:逐点跨线程调用会产生大量消息调度开销,导致绘制时间被拉长;批量添加只需要一次重绘,速度能回到预期的20秒左右。 await Task.Yield():确保后续代码在UI线程执行,避免跨线程操作控件的异常。
另外,可简化无意义的async void的CallBack方法:
private void CallBack() { DrawChartEKG(parsedEKGData, counter, loopToStopAt); }
内容的提问来源于stack exchange,提问作者William J. Starck
相关产品推荐
相关产品推荐

