You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

绘制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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 16:14:58