为什么会收到“ System.ArgumentOutOfRangeException”?

时间:2018-11-29 17:11:11

标签: c# winforms datagridview

我正在处理服务器-客户端应用程序。问题与WindowsFroms客户端异常有关,我不知道为什么会发生。
上下文:服务器存储车道警报。每个通道都有一个laneID,每个通道可以“生成”更多警报。每个警报都有唯一的ID(GUID)。通常,客户端仅从服务器请求警报的向导,并且仅完全获取客户端不存在的那些警报。 我想有效地跟踪DataGridView控件中客户端的服务器警报。但是有时候当我试图从DataGridView中删除一行时,我遇到了'System.ArgumentOutOfRangeException',但我不知道为什么会这样,因为我在那之前找到了该行的那个元素。
我只共享客户端代码。第一个是计时器滴答事件处理程序方法。简而言之,这从用户收集了LaneID,然后从服务器请求属于给定通道之一的所有Guid。将我们的指南与新指南进行比较,并请求新警报。最后,将每个元素添加到控件行。

Task task;
private ConcurrentDictionary<string, string> alarmGuidLaneIdDict;

//Windows.Forms.Timer's Tick method with 300 msec interval
private void AlarmRequestTimer_Tick(object sender, EventArgs e)
    {
        //collecting the id-s
        List<string> laneids = new List<string>();
        foreach (LaneView item in layoutPanel.Controls)
        {
            if (item.laneInfo != null)
            {
                laneids.Add(item.laneInfo.Id);
            }
        }
        if (task == null || task.IsCompleted)
        {
            task = new Task(() =>
            {
            //should not do anything if no id present
                if (laneids.Count < 1)
                    return;

                    //getting all existing guids from server for given id-s
                ConcurrentDictionary<string, string> allGuids = Program.webClient.GetAllAlarmGuids(laneids);

                //should select only guids which I dont own
                ConcurrentDictionary<string, string> requiredAlarms = new ConcurrentDictionary<string, string>();    

                //check each of my guids whether it exists in the collection returned by the server
                    foreach (var item in alarmGuidLaneIdDict)
                {
                    if (!allGuids.ContainsKey(item.Key))
                    {

                    //if not exists at serverside then I remove it from my collection
                        alarmGuidLaneIdDict.TryRemove(item.Key, out string tempLaneId);
                        Debug.WriteLine("Item removed from list, because it is not present on the server.");

                        //now lets find the given nonexisting element in my DataGridWiew
                        int rowIdx = 0; int foundIdx = -1; bool found = false;
                        while (rowIdx < myDataGridView.RowCount && !found)
                        {
                            if (found = (myDataGridView.Rows[rowIdx].Cells[6].Value as NetworkChannel.HttpMessages.FullSpeedInfo).guid == item.Key)
                            {
                                foundIdx = rowIdx;
                            }
                            rowIdx++;
                        }
                        if (found)
                        {                                
                            myDataGridView.BeginInvoke(new MethodInvoker(() =>
                            {
                                //sometimes argument out of range exception, eg foundIdx = 2 when myDataGridView.Rows.Count = 2
                                myDataGridView.Rows.RemoveAt(foundIdx);
                            }));
                        }
                    }
                }
                //for each element that exists at serverside but not at clientside
                foreach (var responseAlarm in allGuids)
                {
                    if (!alarmGuidLaneIdDict.ContainsKey(responseAlarm.Key))
                    {
                        requiredAlarms.TryAdd(responseAlarm.Key, responseAlarm.Value);
                        alarmGuidLaneIdDict.TryAdd(responseAlarm.Key, responseAlarm.Value);
                        Debug.WriteLine("Item added: " + responseAlarm.Key);
                    }
                }
               //completely getting only elements which exists at serverside
                RefreshAlarms(Program.webClient.GetAlarms(requiredAlarms));
            });
            task.Start();
        }
    }

和其他方法:

private void RefreshAlarms(List<NetworkChannel.HttpMessages.FullSpeedInfo> alarms)
        {

            if (myDataGridView.InvokeRequired)
            {
                myDataGridView.BeginInvoke(new MethodInvoker(() => { RefreshAlarms(alarms); }));
            }
            else
            {                
                foreach (NetworkChannel.HttpMessages.FullSpeedInfo alarm in alarms)
                {
                    //do nothing yet
                    OnSpeedingEvent(alarm);
                }

                if (alarms == null)
                    return;
                //adding each new alarm to the DataGridView, the last cell is the object itself
                for (int i = 0; i < alarms.Count; i++)
                {
                    if (alarms[i].alarmInfo == null || string.IsNullOrEmpty(alarms[i].guid))
                        continue;

                    myDataGridView.Rows.Add("Play", alarms[i].guid, alarms[i].timeStamp.ToString(), ((int)alarms[i].speed).ToString() + " km/h", alarms[i].laneInfo.location, alarms[i].GetPriority(), alarms[i]);

                    int rowCount = myDataGridView.Rows.Count;
                    if (alarms[i].GetPriority() == "Significant")
                        myDataGridView.Rows[rowCount - 1].DefaultCellStyle.BackColor = Color.Orange;
                    else if (alarms[i].GetPriority() == "Important")
                        myDataGridView.Rows[rowCount - 1].DefaultCellStyle.BackColor = Color.OrangeRed;
                    else if (alarms[i].GetPriority() == "Warning")
                        myDataGridView.Rows[rowCount - 1].DefaultCellStyle.BackColor = Color.Yellow;
                }
            }
        }

我已经尝试清除每个刻度中的整个DataGridView,然后立即添加每个警报,但是随后闪烁并重置滚动条,使其无用。 任何帮助我解决异常的建议或使DataGridView保持最新状态的其他概念都将受到赞赏。
其他信息:警报对象包含图像数据。服务器中存储的对象数量各不相同,可以每分钟增加+5新警报到+100警报/分钟,几乎是随机的。客户端可以从DataGridView中删除任何选定的项目。

2 个答案:

答案 0 :(得分:0)

在使用AUDIT然后使用Task.Start删除项目时,这些控件上有许多多个线程在运行,这也使用了单独的线程。

因此,很难判断代码中所发生的事情是否将按预期的顺序发生。您可能会在第3个位置找到要删除的项目,但是在删除之前,另一个项目也已删除,现在您要删除的项目在第2个位置,第3个位置没有任何内容,这将为您提供{ {1}}。

当发生此类线程问题时,它们不一致发生的情况非常普遍。这可以解释为什么错误有时“发生”。

解决此问题的一种方法是确保您不会同时做这三件事:

  • 添加行
  • 遍历行并找出要删除的行
  • 删除行

您可以使用BeginInvoke解决此问题,以确保一次仅执行其中一项操作。

此外,与其在发现要删除的行时删除行,不如建立行列表,并在完成后立即删除所有行,请注意始终以降序删除它们。否则,如果您尝试删除第10行,然后删除第11行,则在删除第10行之后,现在位于11的行现在位于10。如果删除11,则删除的是位于12的行。否则就没有11并且您会再次得到例外。

答案 1 :(得分:0)

增加“刻度”间隔并没有帮助我。但, 我找到了异常的确切原因和解决方案。 当我打电话

myDataGridView.BeginInvoke(new MethodInvoker(() =>
                            {                                
                                myDataGridView.Rows.RemoveAt(foundIdx);
                            }));

多次,则它们的执行顺序和时间不确定,因为它们是异步调用。在将myDataGridView.BeginInvoke(...)替换为myDataGridView.Invoke(...)之后,不再发生异常。

感谢您指出正确的方向:)