C #: Как я могу решить "Коллекция была изменена" в обратном вызове отчета о ходе работы BackgroundWorker? - PullRequest
0 голосов
/ 02 октября 2010

Я довольно часто использовал BackgroundWorkers, но никогда раньше не сталкивался с этой проблемой. Моя программа анализирует выходные данные логического анализатора, генерирующего пакеты, которых насчитывается тысячи. Чтобы предотвратить слишком большую задержку обновления ListView в моей форме (я ранее сообщал о каждом из них, как он был найден, и форма полностью не отвечала) я собираю пакеты внутри BackgroundWorker в общий список (List ) затем сообщая, что либо когда найдено n значений (в настоящее время 250), либо когда возникает исключение, либо когда оно завершается.

Проблема возникает в моем обратном вызове, когда я выполняю итерацию по списку. Я получаю исключение InvalidOperationException с ошибкой "Коллекция была изменена". Я не касаюсь коллекции внутри foreach (я добавляю в другую коллекцию, но не вижу причин, по которым это могло бы изменить коллекцию, которую я перебираю - плюс ее комментирование не решает проблему.) даже попытался заблокировать e.UserState, сохранить e.UserState в локальной области видимости List и заблокировать его, кажется, ничего не работает.

Вот код для моего метода обратного вызова:

void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    progressBar.Value = e.ProgressPercentage;
    packetsListView.SuspendLayout();
    lock ((List<Packet>)e.UserState)
    {
        foreach (Packet packet in (List<Packet>)e.UserState)
        {
            packets.Add(packet);
            ListViewItem item = new ListViewItem(string.Format("{0}ns", Math.Round(packet.StartSampleNumber * 41.666667)));
            item.Tag = packet;
            item.SubItems.Add(new ListViewItem.ListViewSubItem(item, packet.Description));
            packetsListView.Items.Add(item);
        }
    }
    packetsListView.ResumeLayout();

    statusLabel.Text = string.Format("Analyzing...found {0} {1}", packetsListView.Items.Count, packetsListView.Items.Count == 1 ? "packet" : "packets");
}

Ответы [ 4 ]

5 голосов
/ 02 октября 2010

Простое объяснение вашей проблемы состоит в том, что вы используете блокировку в обработчике событий ProgressChanged, но не в обработчике событий DoWork. Что позволяет рабочему потоку по-прежнему изменять коллекцию, пока поток пользовательского интерфейса выполняет ее итерацию.

Это легко решить, просто создайте новый Список <> сразу после вызова ReportProgress на рабочем месте. Теперь поток пользовательского интерфейса - единственный, имеющий ссылку на список, вам больше не нужно использовать блокировку.

0 голосов
/ 02 октября 2010

Создайте packet в этом методе, и тогда он не сможет использовать псевдоним, повторяемый.

0 голосов
/ 02 октября 2010

Я обнаружил, что если производительность не является большой проблемой, то использование некоторой коллекции, безопасной для одновременной работы, работает очень хорошо.Подробнее здесь

0 голосов
/ 02 октября 2010

Вы не можете изменять коллекцию, для которой вы выполняете итерацию, с помощью foreach.Вы вызываете метод packets.Add внутри цикла foreach, и я подозреваю, что эта переменная packets указывает на ту же коллекцию, по которой вы выполняете итерацию.

Если это не так, вы можете попробовать заблокироватьличное статическое поле, которое вы объявляете внутри формы:

private static object _syncRoot = new object();

, а затем:

lock (_syncRoot)
{
    ...
}
...