Meysam Tolouee Ответов: 2

Как справиться с этой ошибкой: коллекция была изменена; операция перечисления может не выполняться.


Здравствуйте Эксперты

В моем проекте я создаю много транзакций и храню их в dataGridView вместе с их деталями.
При нажатии кнопки startbtn этот код будет выполнен:
Manager manager = new Manager(GridView);

А вот и класс менеджера:
public class Manager 
{
  public static Dictionary<Thread, int> ThreadList;
  public static DataTable DisposableThreads;

  
  public Manager(DataGridView dgv)
  {
     ThreadList = new Dictionary<Thread, int>();
     DisposableThreads = new DataTable();
     DisposableThreads.Columns.Add(new DataColumn("Thread", typeof(Thread)));

     DisposableThreads.RowChanged += RowChanged;
   
     foreach (DataGridViewRow row in dgv.Rows)
     {
        Thread thread = new Thread(() => Transaction(dgv, (row as GridRow).Row));
   
        ThreadList.Add(thread, (row as GridRow).Row);
        thread.Start();
     }            
  }

  private void RowChanged(object sender, DataRowChangeEventArgs e)
  {
     if (e.Action == DataRowAction.Add)
     {
        ThreadList.Remove((Thread)e.Row.ItemArray[0]);
        DisposableThreads.Rows.Remove(e.Row);
     }
  }

  public void Transaction(DataGridView dataGridView, int row)
  {
     // Some Code
     DisposableThreads.Rows.Add(new object[] { Thread.CurrentThread });
  }
}

Все идет прекрасно.
Но иногда, когда я нажимаю кнопку stop, чтобы отменить запущенные потоки, происходит следующий exeception.
Collection was modified; enumeration operation may not execute.

Код в кнопке Стоп таков:
private void Stop_Click(object sender, EventArgs e)
{
  try
  {
     foreach (Thread t in Manager.ThreadList.Keys)
     {
        if (t.ThreadState == System.Threading.ThreadState.Running)
           t.Abort(); // they really do not abort by this code !!??

     } 
     MessageBox.Show("All operations canceled successfully.");
  }
  catch (Exception ex)
  {
     MessageBox.Show(ex.Message);
  }
}

johannesnestler

Sounds like a little confusion cause threads are involved etc. But the cause of the error is quite simple. You are changing a collection while you are enumerating it - this is not allowed in C#. Ashok Rathod showed you a way to circumenvent this problem by creating a temp list... I just want to add the answer to your commented question: No, Thread.Abort just throws an exception on the aborted thread, you have to handle the exception in your threads code for yourself. So nothing will be aborted (but it will may fail due unhandled excpetion) if you don't "react" to it in your code. The best advice if in doubt: have a look at MSDN! (Thread.Abort is well documented, as most parts of .NET Framework are)

Meysam Tolouee

Я знаю, что причина ошибки проста. Что не так просто, так это ответ.

johannesnestler

Привет Мейсам,
Теперь, когда я поближе присмотрелся к решению Ашока, я увидел проблему. Вы должны создать независимую(!) коллекцию с потоками для прерывания (своего рода моментальный снимок в то время). После этого попробуйте прервать все потоки, не перечисляя новую независимую коллекцию (используйте цикл for,а не foreach).

Meysam Tolouee

Смотрите мое решение.

2 Ответов

Рейтинг:
5

Meysam Tolouee

Я могу изменить способ stop_Click по этому пути:

try
{
   while (Manager.ThreadList.Count != 0)
   {
      var t = Manager.ThreadList.FirstOrDefault(x => (x.Key as Thread).IsAlive == true);
                   
      Manager.DisposableThreads.Rows.Add(t.Key);
   }
  
   MessageBox.Show("All operation canceled successfully.");
}
catch (Exception ex)
{
   MessageBox.Show(ex.Message);
}

Кроме того метод RowChanged изменяется следующим образом:
private void RowChanged(object sender, DataRowChangeEventArgs e)
{
  if (e.Action == DataRowAction.Add)
  {
      if (((Thread)e.Row.ItemArray[0]).IsAlive)
          ((Thread)e.Row.ItemArray[0]).Abort();

      ThreadList.Remove((Thread)e.Row.ItemArray[0]);
      DisposableThreads.Rows.Remove(e.Row);
    }
}


johannesnestler

приятно слышать, что вы решили эту проблему - выглядит хорошо

Рейтинг:
0

ashok rathod

Похоже, что в вашем щелчке кнопки остановки вы получаете доступ к менеджеру.ThreadList.Ключи
и его статический объект (это означает, что он обрабатывается системой), поэтому, когда вы используете объект словаря, он может быть изменен. Таким образом, здесь ситуация такова, что одна функция постоянно изменяет эту коллекцию, а тем временем stop_Click использует ее...
Так что здесь вы можете сделать одну вещь ...




private void Stop_Click(object sender, EventArgs e)
{
  try
  {
    Dictionary<thread,> TempThreadList = new Dictionary<thread,>();
    while(Manager.ThreadList.Keys.Count > 0) //will run upto there is any thread is running.
{
  TempThreadList=Manager.ThreadList; //added one more container for continuosly changing collection.

     foreach (Thread t in TempThreadList.Keys)
     {
        if (t.ThreadState == System.Threading.ThreadState.Running)
           t.Abort(); // they really do not abort by this code !!??

     }
}
     MessageBox.Show("All operations canceled successfully.");
  }
  catch (Exception ex)
  {
     MessageBox.Show(ex.Message);
  }
}


Meysam Tolouee

Я уже пробовал и это; то же самое исключение.