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

C#实时图表更新遇Collection修改异常,ToList()无法解决

问题描述

我用C#写了个从CSV读数据画图表的程序,CSV文件由Windows服务每35ms写入一次。程序每秒读取文件更新数据列表,实现每秒刷新的实时图表。一次性读取全部数据时图表正常,但定时读取更新列表时,立刻抛出“Collection was modified; enumeration operation may not execute”异常。已经在foreach里加了.ToList(),但问题还是没解决。

相关代码

public Form1()
{
    InitializeComponent();
    this.Load += Form1_Load;
}
System.Timers.Timer timer_Chart_update = new System.Timers.Timer();

private void Form1_Load(object sender, EventArgs e)
{
    this.Controls.Add(chart2);
    Init_Chart_Update_Timer();
}    

public static List<double> List_Position = new List<double>();
public static List<double> List_Current = new List<double>();
public static List<double> List_X_Axis = new List<double>();

public void Init_Chart_Update_Timer()
{
    timer_Chart_update.Elapsed += new ElapsedEventHandler(OnTimedEvent);
    timer_Chart_update.Interval = 1000;
    timer_Chart_update.Start();
}

private void OnTimedEvent(object sender, EventArgs e)
{
    Read_Test_File();
    Draw_Chart();
}
public void Read_Test_File()
{
    List_Position.Clear();
    try
    {
        using (var fs = new FileStream("D:\\ConCheetah\\Test4\\test_files\\test_01.csv", FileMode.Open, FileAccess.Read, FileShare.ReadWrite))              
        using (var streamReader = new StreamReader(fs, Encoding.UTF8, true))
        {
            String line;
            while ((line = streamReader.ReadLine()) != null)
            {
                if (line == "$")
                {
                    break;
                }
                bool isLetter = !String.IsNullOrEmpty(line) && Char.IsLetter(line[0]);
                if (isLetter == true)
                {
                    continue;
                }
                List_Position.Add(double.Parse(line));
            }
        }
    }
    catch(Exception e)
    {
        using (var writer = new StreamWriter(@"D:\ConCheetah\test4\log6.txt", true))
        {
            writer.WriteLine("File Read Error " + e+ " "+ DateTime.Now.ToString());
        }
    }
}

public void Draw_Chart()
{
    chart2.Series.Clear();
    chart2.Titles.Clear();
    chart2.Legends.Clear();
    var newSeries_1 = new Series();
    var newSeries_2 = new Series();
    newSeries_1.ChartType = SeriesChartType.Line;
    newSeries_2.ChartType = SeriesChartType.Line;
    chart2.Series.Add(newSeries_1);
    chart2.Series.Add(newSeries_2);
    List_X_Axis.Clear();

    for (int w = 0; w <= 940; w++)
    {
        List_X_Axis.Add(w);
    }

    var t = chart2.Titles.Add("Title1");
    t.Text =  "  Position/Current";
    t.ForeColor = Color.Silver;
    chart2.ChartAreas[0].AxisX.Title = ".";
    chart2.ChartAreas[0].AxisY.Title = "mm";
    chart2.ChartAreas[0].AxisY.TitleForeColor = Color.Orange;
    chart2.ChartAreas[0].AxisY.LabelStyle.ForeColor = Color.Orange;
    chart2.ChartAreas[0].AxisX.Minimum = 0d;
    chart2.Series[0].Color = Color.Orange;
    chart2.ChartAreas[0].AxisY.Minimum = 0;
    chart2.ChartAreas[0].AxisY.Maximum = 15;
    chart2.ChartAreas[0].AxisY2.Enabled = AxisEnabled.True;
    chart2.ChartAreas[0].AxisY2.Minimum = -350;
    chart2.ChartAreas[0].AxisY2.Maximum = 350;
    chart2.ChartAreas[0].AxisY2.Title = "% of Max. Current";
    chart2.ChartAreas[0].AxisY2.TitleForeColor = Color.Yellow;
    chart2.ChartAreas[0].AxisY2.LabelStyle.ForeColor = Color.Yellow;
    chart2.ChartAreas[0].AxisY2.MajorGrid.LineColor = Color.Silver;
    chart2.ChartAreas[0].AxisY2.MajorTickMark.LineColor = Color.Silver;
    chart2.Series[1].YAxisType = AxisType.Secondary;
    chart2.Series[1].Color = Color.Yellow;

    chart2.ChartAreas[0].BackColor = Color.Black;

    int x = 1;
    foreach (float v in List_Position.ToList())
    {
        newSeries_1.Points.AddXY(x, v);
        x++;
    }
    int x2 = 1;
    foreach (float v in List_Position.ToList())
    {
        newSeries_2.Points.AddXY(x2, v);
        x2++;
    }
}

异常堆栈信息

System.InvalidOperationException: 集合已修改;枚举操作可能无法执行。
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.List1.Enumerator.MoveNextRare() at System.Collections.Generic.List1.Enumerator.MoveNext()
at System.Windows.Forms.DataVisualization.Charting.Series.ResetAutoValues(Boolean reset)
at System.Windows.Forms.DataVisualization.Charting.Series.UnPrepareData(ISite controlSite)
at System.Windows.Forms.DataVisualization.Charting.Data.DataManager.ChartPicture_AfterPaint(Object sender, ChartPaintEventArgs e)
at System.Windows.Forms.DataVisualization.Charting.ChartPicture.OnAfterPaint(ChartPaintEventArgs e)

解决方案

1. 解决线程冲突问题

System.Timers.Timer的Elapsed事件在非UI线程触发,直接操作WinForms控件和共享集合会导致线程安全问题——图表在UI线程绘制时,定时器线程可能正在修改相关集合。

修改OnTimedEvent方法,切换到UI线程执行:

private void OnTimedEvent(object sender, EventArgs e)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action(() => 
        {
            Read_Test_File();
            Draw_Chart();
        }));
    }
    else
    {
        Read_Test_File();
        Draw_Chart();
    }
}

2. 给集合操作加锁保证原子性

静态共享集合List_Position的读写操作可能并发执行,加锁避免同时修改和读取:

// 定义全局锁对象
private static readonly object _lockObj = new object();

public void Read_Test_File()
{
    lock(_lockObj)
    {
        List_Position.Clear();
        try
        {
            // 原读取逻辑不变
        }
        catch(Exception e)
        {
            // 原异常处理不变
        }
    }
}

public void Draw_Chart()
{
    // ... 原初始化代码不变 ...

    lock(_lockObj)
    {
        // 复制一份集合,避免绘制时原集合被修改
        var positionCopy = new List<double>(List_Position);
        
        int x = 1;
        foreach (var v in positionCopy)
        {
            newSeries_1.Points.AddXY(x, v);
            x++;
        }
        int x2 = 1;
        foreach (var v in positionCopy)
        {
            newSeries_2.Points.AddXY(x2, v);
            x2++;
        }
    }
}

3. 改用WinForms专用定时器

直接用System.Windows.Forms.Timer,它的Tick事件在UI线程触发,从根源避免线程切换问题:

// 替换原定时器声明
private System.Windows.Forms.Timer timer_Chart_update = new System.Windows.Forms.Timer();

public void Init_Chart_Update_Timer()
{
    timer_Chart_update.Tick += OnTimedEvent;
    timer_Chart_update.Interval = 1000;
    timer_Chart_update.Start();
}

// 修改事件方法签名
private void OnTimedEvent(object sender, EventArgs e)
{
    Read_Test_File();
    Draw_Chart();
}

关键说明

异常堆栈显示问题出在图表控件的AfterPaint阶段,说明绘制时相关集合(比如Series的Points或绑定的数据源)被修改了。线程同步和集合操作的原子性是解决问题的核心,即使加了.ToList(),如果原集合在复制后被修改,图表控件内部仍可能引用原集合进行计算,导致异常。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 18:26:03