删除数据表中的行时 DataGridView 出现无法捕获的异常

Uncatchable exception in DataGridView when deleting rows in datatable

我有一个数据 table,其中的行是根据 Internet 上可用的数据添加的。每次添加一行时,我还会在该行上放置一个时间戳。这已经工作了很多年,数百名用户没有发生任何错误。我现在希望根据数据的年龄从数据 table 中删除行。我有一个计时器,它触发每个用户定义的时间间隔,然后我搜索所有较旧的行并删除它们。问题是,我收到了各种错误,这些错误似乎在发生变化,即使我在所有方法上都有 try catch 语句,也没有捕获到错误。该错误仅在 program.cs 中整个应用程序的错误捕获中被捕获 错误是“退出错误:对象引用未设置到对象的实例。在 System.Windows.Forms.DataGridViewCell.PaintWork, 或退出错误:索引超出范围。必须为非负数且小于集合的大小。 参数名称:index at System.Collections.ArrayList.get_Item(Int32 index)

或 退出错误:对象引用未设置到对象的实例。在 System.Windows.Forms.DataGridViewTextBoxCell.PaintPrivate

但其余错误中没有任何有用的信息。 这是代码:

    public void ageTimer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
    {
        try
        {
            spotAge = Convert.ToDouble(Properties.Settings.Default.SpotAge);

            //Select all rows in datatable where the age is older than spotAge and delete them.
            //date is stored in dt as 20:12:2017: 21:49:02 derived from DateTime.UtcNow.ToString("dd:MM:yyyy: HH:mm:ss")

            //spotTime is the time from the table

            DateTime spotTime;
            dt.AcceptChanges();
            var rowCount = dt.Rows.Count;

            for (int i = rowCount -1; i >= 0; i--)
            {
                DataRow dr = dt.Rows[i];

                spotTime = DateTime.ParseExact(Convert.ToString(dr["date"]), "dd:MM:yyyy: HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);

                if (spotTime.AddMinutes(spotAge) <= DateTime.UtcNow)
                {
                    dr.Delete();

                }
            }

            dt.AcceptChanges();

            dataGridView1.Update();
            dataGridView1.Refresh();
            dataGridView1.FirstDisplayedScrollingRowIndex = 0;
        }

        catch (Exception x)
        {

        }
    }

现在有趣的是,在我的系统上,这根本不会崩溃。但是,如果我 运行 visual studio 在调试模式下它会。它也会在少数用户身上崩溃。但不是每个人。

我尝试了相同的代码,只是我总是只删除一行并且它工作得很好。我尝试删除除第一行、前两行之外的每一行,但它仍然崩溃。

根据下面的评论指出计时器 运行ning 在一个单独的线程上,我添加了以下内容:

    public void ageTimer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
    {
        DeleteRows("");
    }

上面的代码现在有以下内容:

private void DeleteRows(string details)
{
  if (InvokeRequired)
  {
     this.Invoke(new Action<string>(DeleteRows), new object[] { details});
     return;
  }

其余代码同上。这完全解决了这个问题。 非常感谢!

这里的问题是,您在 for 循环中调用 delete row.delete 语句。这是在操纵行集合的长度,因此您会得到索引超出范围的异常。

引用自 msdn:

Delete should not be called in a foreach loop while iterating through a DataRowCollection object. Delete modifies the state of the collection.

再读一遍你的问题。这工作了很多年。现在它不起作用,因为您在新的单独线程中添加了计时器 运行。 这可能是竞争条件。 显然,dt 是一种共享资源,在多线程环境中使用不当。 假设 dt 是你的数据网格视图的来源,在一种情况下,用户可能正在用它做一些事情(只需滚动就足以从 dt 读取以便绘制新可见的行 - 因此,我想你得到了PaintWork 错误,因为状态同时被定时器事件处理程序改变了)。想象一下,用户从网格中删除行,这反映到底层源 dt,但是,您的计时器恰好在刚才删除了这些行,并且您得到索引超出范围异常。

除了在你的定时器事件处理程序中使用dt,你可以尝试制作它的副本dt_copy(注意不要制作浅拷贝)。进行更改,完成后,只需将该副本绑定为 datagridview 的新源,一切都应该很好。

另一种可能执行得更快的方法是调用存储过程并直接通过数据库进行删除,并在调用 returns 后刷新 dt(只需重新填充 dt ).

在这两种情况下,根据删除速度的不同,您应该检查 'dt' 是否脏(同时由用户更新)并且可能希望合并这些更改以免丢失。