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),导致数据量差异,加重渲染负担。
具体修复步骤
修复if语句逻辑
给多Series移除代码添加大括号,确保仅当点数超限时执行:if (chart1.Series[0].Points.Count > 100) { chart1.Series[0].Points.RemoveAt(0); chart1.Series[1].Points.RemoveAt(0); }复用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);
- 先在类中声明成员变量:
优化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; } }
- 启用双缓冲减少闪烁:
串口数据处理优化
把串口解析的数据放到线程安全队列,让Timer线程统一处理UI更新,减少跨线程操作的开销。
内容的提问来源于stack exchange,提问作者SludyAsh
相关产品推荐
相关产品推荐

