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

VS2022中C# WinForms单图表多系列实时更新时窗体卡顿无响应

单个Chart多Series实时显示卡顿问题

刚用VS2022做项目,遇到个棘手问题:想在单个Chart里实时显示多个Series,单个Series运行正常,但加多个后,窗体几秒就卡顿,没法移动、调整大小,还没错误提示。因为空间限制不能用多个独立Chart,试过下面两种操作都没用:

  • 只给一个Series移除旧数据点,问题依旧
  • 同时给多个Series移除旧数据点,问题仍存在
    但单独测试图表相关代码却能正常运行。相关代码如下:

原始完整代码

using GeoCoordinatePortable;
using GMap.NET;
using System.Data;
using System.Security.Cryptography.X509Certificates;
using System.IO.Ports;
using System.Globalization;
using System.Windows.Forms;
using System.Text;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using Timer = System.Windows.Forms.Timer;

namespace BaseStation_Cervus1
{
    public partial class Form1 : Form
    {
        GMap.NET.WindowsForms.GMapControl gMapControl;

        private SerialPort myport;
        private string in_data;

        double x;

        int indexV = 0;
        List<double> alt = new List<double>();
        List<double> temp = new List<double>();
        List<double> pres = new List<double>();
        //List<double> aX = new List<double>();
        //List<double> aY = new List<double>();
        //List<double> aZ = new List<double>();
        //List<double> gX = new List<double>();
        //List<double> gY = new List<double>();
        //List<double> gZ = new List<double>();
        public Form1()
        {
            InitializeComponent();
            port_cb.Text = "COM5";

            gMapControl = new GMap.NET.WindowsForms.GMapControl();
            gMapControl.MapProvider = GMap.NET.MapProviders.GMapProviders.GoogleSatelliteMap;
            gMapControl.Dock = DockStyle.Fill;
            gMapControl.MapProvider = GMap.NET.MapProviders.BingMapProvider.Instance;
            GMap.NET.GMaps.Instance.Mode = GMap.NET.AccessMode.ServerAndCache;
            gMapControl.ShowCenter = false;
            gMapControl.MinZoom = 1;
            gMapControl.MaxZoom = 20;
            gMapControl1.Controls.Add(gMapControl);
            gMapControl.Position = new PointLatLng(29.16, -111.01);
            gMapControl.Zoom = 15;
            gMapControl.Update();
            gMapControl.Refresh();
            var markersOverlay = new GMap.NET.WindowsForms.GMapOverlay("You");
            var marker = new GMap.NET.WindowsForms.Markers.GMarkerGoogle(new PointLatLng(29.16, -111.01), GMap.NET.WindowsForms.Markers.GMarkerGoogleType.purple);
            markersOverlay.Markers.Add(marker);
            gMapControl.Overlays.Add(markersOverlay);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            timer1.Tick += timer1_Tick;
            timer1.Interval = 50;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            myport = new SerialPort();
            myport.BaudRate = 9600;
            myport.PortName = port_cb.Text;
            myport.Parity = Parity.None;
            myport.DataBits = 8;
            myport.StopBits = StopBits.One;
            myport.DataReceived += Myport_DataReceived;

            try
            {
                if (!myport.IsOpen)
                {
                    myport.Open();
                    MessageBox.Show("Connected Successfully");
                    textBox1.Text = " ";
                    timer1.Start();
                }
            }
            catch
            {
                myport.Close();
                MessageBox.Show("Connection has closed, try again");
            }
        }

        private void Myport_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            try
            {
                in_data = myport.ReadLine();
                this.Invoke(new EventHandler(displaydata_event));
                SaveData();
            }
            catch (Exception)
            {
                MessageBox.Show("Error!");
            }
        }

        private void SaveData()
        {
            var foundIndexes = new List<int>();
            for (int i = 0; i < in_data.Length; i++)
            {
                if (in_data[i] == '/')
                    foundIndexes.Add(i);
            }
            try
            {
                int range1 = foundIndexes[1] - foundIndexes[0];
                int range2 = foundIndexes[2] - foundIndexes[1];
                int range3 = foundIndexes[3] - foundIndexes[2];
                int range4 = foundIndexes[4] - foundIndexes[3];
                int range5 = foundIndexes[5] - foundIndexes[4];
                //int range6 = foundIndexes[6] - foundIndexes[5];
                //int range7 = foundIndexes[7] - foundIndexes[6];
                //int range8 = foundIndexes[8] - foundIndexes[7];
                //int range9 = foundIndexes[9] - foundIndexes[8];
                //int range10 = foundIndexes[10] - foundIndexes[9];
                //int range11 = foundIndexes[11] - foundIndexes[10];
                temp.Add(Convert.ToDouble(in_data.Substring(foundIndexes[0] + "/".Length, range1 - "C/".Length)));
                pres.Add(Convert.ToDouble(in_data.Substring(foundIndexes[1] + "/".Length, range2 - "Pa/".Length)));
                alt.Add(Convert.ToDouble(in_data.Substring(foundIndexes[2] + "/".Length, range3 - "m/".Length)));
                var markersOverlay = new GMap.NET.WindowsForms.GMapOverlay("Rocket");
                var marker = new GMap.NET.WindowsForms.Markers.GMarkerGoogle(new PointLatLng(Convert.ToDouble(in_data.Substring(foundIndexes[3] + "/".Length, range4 - "/".Length)), Convert.ToDouble(in_data.Substring(foundIndexes[4] + "/".Length, range5 - "/".Length))), GMap.NET.WindowsForms.Markers.GMarkerGoogleType.blue);
                markersOverlay.Markers.Add(marker);
                gMapControl.Overlays.Add(markersOverlay);
                //aX.Add(Convert.ToDouble(in_data.Substring(foundIndexes[5] + "/".Length, range6 - "/".Length)));
                //aY.Add(Convert.ToDouble(in_data.Substring(foundIndexes[6] + "/".Length, range7 - "/".Length)));
                //aZ.Add(Convert.ToDouble(in_data.Substring(foundIndexes[7] + "/".Length, range8 - "/".Length)));
                //gX.Add(Convert.ToDouble(in_data.Substring(foundIndexes[8] + "/".Length, range9 - "/".Length)));
                //gY.Add(Convert.ToDouble(in_data.Substring(foundIndexes[9] + "/".Length, range10 - "/".Length)));
                //gZ.Add(Convert.ToDouble(in_data.Substring(foundIndexes[10] + "/".Length, range11 - "/".Length)));
            }
            catch
            {
                label3.BeginInvoke(new Action(() => { label3.Text = "Noise"; }));
            }
        }

        private void displaydata_event(object? sender, EventArgs e)
        {
            textBox1.AppendText(in_data + "\n");
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (indexV < alt.Count && alt.Count > 0)
            {
                //alt, temp, press
                chart1.Series[0].Points.AddXY(x, alt[indexV]);
                chart1.Series[1].Points.AddXY(x, temp[indexV]);

                if (chart1.Series[0].Points.Count > 100)
                    chart1.Series[0].Points.RemoveAt(0);

                chart1.ChartAreas[0].AxisX.Minimum = chart1.Series[0].Points[0].XValue;
                chart1.ChartAreas[0].AxisX.Maximum = x;

                //aceleration
                //chart2.Series[0].Points.AddXY(x, aX[indexV]);

                //if (chart2.Series[0].Points.Count > 100)
                //{
                //    chart2.Series[0].Points.RemoveAt(0);
                //}

                //chart2.ChartAreas[0].AxisX.Minimum = chart2.Series[0].Points[0].XValue;
                //chart2.ChartAreas[0].AxisX.Maximum = x;

                //rotation
                //chart3.Series[0].Points.AddXY(x, gX[indexV]);

                //if (chart3.Series[0].Points.Count > 100)
                //    chart3.Series[0].Points.RemoveAt(0);

                //chart3.ChartAreas[0].AxisX.Minimum = chart3.Series[0].Points[0].XValue;
                //chart3.ChartAreas[0].AxisX.Maximum = x;

                indexV += 1;
                x += 1;

            }
        }
    }

}

尝试添加第二个Series的代码片段

if (indexV < alt.Count && alt.Count > 0)
{
    //alt, temp, press
    chart1.Series[0].Points.AddXY(x, alt[indexV]);
    chart1.Series[1].Points.AddXY(x, temp[indexV]);                

    if (chart1.Series[0].Points.Count > 100)
        chart1.Series[0].Points.RemoveAt(0);

    chart1.ChartAreas[0].AxisX.Minimum = chart1.Series[0].Points[0].XValue;
    chart1.ChartAreas[0].AxisX.Maximum = x;

    indexV += 1;
    x += 1;

}

尝试移除多Series旧数据点的代码片段

if (indexV < alt.Count && alt.Count > 0)
{
    //alt, temp, press
    chart1.Series[0].Points.AddXY(x, alt[indexV]);
    chart1.Series[1].Points.AddXY(x, temp[indexV]);            

    if (chart1.Series[0].Points.Count > 100)
        chart1.Series[0].Points.RemoveAt(0);
        chart1.Series[1].Points.RemoveAt(0);

    chart1.ChartAreas[0].AxisX.Minimum = chart1.Series[0].Points[0].XValue;
    chart1.ChartAreas[0].AxisX.Maximum = x;

    indexV += 1;
    x += 1;

}

单独测试的图表代码

using System;

namespace TestChart
{
    public partial class Form1 : Form
    {

        double x;

        int indexV = 0;
        List<double> alt = new List<double>();
        List<double> temp = new List<double>();
        public Form1()
        {
            InitializeComponent();
            timer1.Start();
            timer2.Start();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            timer1.Tick += timer1_Tick;
            timer1.Interval = 50;
            timer2.Tick += timer2_Tick;
            timer2.Interval = 10;
        }

        private void timer1_Tick(object sender, EventArgs e)
        {
            if (indexV < alt.Count && alt.Count > 0)
            {
                //alt, temp, press
                chart1.Series[0].Points.AddXY(x, alt[indexV]);
                chart1.Series[1].Points.AddXY(x, temp[indexV]);

                if (chart1.Series[0].Points.Count > 100)
                    chart1.Series[0].Points.RemoveAt(0);

                chart1.ChartAreas[0].AxisX.Minimum = chart1.Series[0].Points[0].XValue;
                chart1.ChartAreas[0].AxisX.Maximum = x;

                indexV += 1;
                x += 1;
            }
        }

        private void timer2_Tick(object sender, EventArgs e)
        {
            alt.Add(3 * Math.Sin(5 * x));
            temp.Add(3 * Math.Cos(5 * x));
        }
    }
}

问题分析与解决方案

核心问题1:代码缩进导致的逻辑错误

尝试移除多Series旧数据点的代码中,chart1.Series[1].Points.RemoveAt(0);未被包含在if代码块内(缺少大括号),这会导致每次Timer Tick都执行移除操作,不管Series点数是否超过100,最终引发数据不同步和频繁UI更新,直接加剧卡顿。

核心问题2:GMap控件的Overlay持续堆积

SaveData方法中,每次收到串口数据都会创建新的GMapOverlay并添加到控件,导致Overlay数量持续增长,GMap需要渲染的标记越来越多,这才是卡顿的主要原因——多Series只是让UI负载更高,问题更快暴露。

核心问题3:Chart更新未做优化

每次添加数据后手动设置AxisX的Min/Max会触发多次重绘;且多Series数据清理不同步(仅清理Series0),导致数据量差异,加重渲染负担。


具体修复步骤

  1. 修复if语句逻辑
    给多Series移除代码添加大括号,确保仅当点数超限时执行:

    if (chart1.Series[0].Points.Count > 100)
    {
        chart1.Series[0].Points.RemoveAt(0);
        chart1.Series[1].Points.RemoveAt(0);
    }
    
  2. 复用GMapOverlay
    不要每次创建新Overlay,复用同一个Overlay更新标记:

    • 先在类中声明成员变量:
      private GMap.NET.WindowsForms.GMapOverlay _rocketOverlay;
      
    • 在Form构造函数中初始化并添加到控件:
      _rocketOverlay = new GMap.NET.WindowsForms.GMapOverlay("Rocket");
      gMapControl.Overlays.Add(_rocketOverlay);
      
    • 在SaveData中更新标记:
      _rocketOverlay.Markers.Clear();
      var marker = new GMap.NET.WindowsForms.Markers.GMarkerGoogle(
          new PointLatLng(Convert.ToDouble(in_data.Substring(foundIndexes[3] + "/".Length, range4 - "/".Length)), 
          Convert.ToDouble(in_data.Substring(foundIndexes[4] + "/".Length, range5 - "/".Length))), 
          GMap.NET.WindowsForms.Markers.GMarkerGoogleType.blue);
      _rocketOverlay.Markers.Add(marker);
      
  3. 优化Chart更新

    • 启用双缓冲减少闪烁:
      // 在Form构造函数中添加
      chart1.DoubleBuffered = true;
      
    • 批量更新减少重绘:
      private void timer1_Tick(object sender, EventArgs e)
      {
          if (indexV < alt.Count && alt.Count > 0)
          {
              chart1.SuspendLayout();
      
              chart1.Series[0].Points.AddXY(x, alt[indexV]);
              chart1.Series[1].Points.AddXY(x, temp[indexV]);
      
              if (chart1.Series[0].Points.Count > 100)
              {
                  chart1.Series[0].Points.RemoveAt(0);
                  chart1.Series[1].Points.RemoveAt(0);
              }
      
              chart1.ChartAreas[0].AxisX.Minimum = chart1.Series[0].Points[0].XValue;
              chart1.ChartAreas[0].AxisX.Maximum = x;
      
              chart1.ResumeLayout();
              chart1.Refresh();
      
              indexV += 1;
              x += 1;
          }
      }
      
  4. 串口数据处理优化
    把串口解析的数据放到线程安全队列,让Timer线程统一处理UI更新,减少跨线程操作的开销。

内容的提问来源于stack exchange,提问作者SludyAsh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 15:32:02