Как добавить тысячи элементов в связанную коллекцию без блокировки GUI
У меня есть настройка, где потенциально тысячи элементов (думаю, 3000-5000) будут добавлены к ObservableCollection
, привязанным к некоторому визуальному интерфейсу. В настоящее время процесс их добавления довольно медленный (около 4 секунд /1000 элементов), и, конечно же, GUI не отвечает за это время. Каков хороший метод обработки перемещения сразу нескольких элементов в коллекцию, не беспокоясь о блокировке системы? Я посмотрел на DispatcherTimer
, но я не уверен, предоставит ли он все, что мне нужно.
Другой вопрос: есть ли что-то, что я могу сделать, чтобы ускорить создание этих объектов, чтобы не потребовалось столько времени, чтобы добавить их в коллекцию? В настоящее время я использую их так: Collection.Add(new Item(<params>))
Будет ли генерировать элементы заранее, в фоновом потоке, вероятно, уменьшить время, необходимое для добавления их заметной суммой?
Изменить: виртуализация невозможна. Требования определяют внешний вид WrapPanel
, поэтому на самом деле дисплей представляет собой ListBox
, у которого есть шаблонная панель ItemsPanel
Edit2: согласно секундомеру, узкое место фактически помещает элементы в мой ObservableCollection
. Я попробую изменить этот тип коллекции и сделать свое собственное уведомление, чтобы убедиться, что это значительно ускоряет его.
Edit3: так ответ в одном месте - я решил эту проблему (с помощью ниже), создав класс, который наследует от ObservableCollection
. Этот класс сделал две вещи: разоблачить метод добавления коллекций за один раз и добавил возможность подавить событие CollectionChanged
. С этими изменениями время, затрачиваемое на добавление 3000 предметов, составляет примерно 0,4 секунды (улучшение на 97%). Эта ссылка содержит информацию об осуществлении этих изменений.
Ответы
Ответ 1
Вы сказали 1000, поэтому я буду придерживаться только этого номера.
IIRC, наблюдаемая коллекция имеет небольшой недостаток - если вы добавляете элементы один за другим, он уведомляет об этом один раз за каждый элемент. Это означает, что у вас есть 1000 уведомлений для 1000 элементов, а поток пользовательского интерфейса будет работать со смертельной скоростью, чтобы не отставать от перерисовки экрана.
Вам нужно перерисовать как можно скорее? Может быть, вы можете заказать дополнения? Разделите 1000 предметов на несколько упакованных из 100 предметов или немного больше пакетов из 50 или 20 предметов. Затем вместо того, чтобы поместить все элементы один за другим, поместите их в пакеты. Но будьте осторожны: вы должны использовать некоторые методы, такие как AddRange, реализованные самим сборником, а не LINQ, иначе вы снова будете иметь одну по одну вставку. Если вы найдете такой метод, он должен значительно сократить количество событий, потому что сбор должен поднять событие Changed только один раз на вызов AddRange.
Если наблюдаемая коллекция не имеет AddRange, либо используйте другую коллекцию, либо напишите ее самостоятельно, вероятно, будет достаточно оболочки. Цель состоит в том, чтобы НЕ повышать событие "Изменено" при каждом добавлении(), но после разумного подсчета их или, может быть, просто пропустить рейз. Изменено при добавлении предметов и повышении Изменено через определенные промежутки времени? Это было бы особенно полезно, если ваши данные "текут" бесконечно с постоянной скоростью.
Конечно, при таком количестве предметов, появляющихся на экране, вы можете так же хорошо удерживаться при рендеринге. Если ваши ItemTemplates сложны, 1000 объектов раз 1000 экземпляров визуальных слоев/свойств могут просто убить пользователя. Вы упростили ItemTemplates до минимума?
Последняя вещь: рассмотрите возможность использования виртуализации StackPanels как ItemPanels в ItemsControl/ListBoxes. Это может значительно уменьшить объем памяти и количество элементов, нарисованных в один момент времени. Это не обязательно поможет в увеличении числа или событий, но это может сильно помочь, если у вас есть сложные шаблоны элементов!
Изменить: вы используете ObservableCollection, поэтому я предположил WPF/Silverlight.. обновить вопрос, если это неверно
Ответ 2
WPF Binding
поддерживает concurrency по этой причине. Попробуйте установить Binding.IsAsync
значение true. Кроме того.
- Не используйте
ObservableCollection<T>
, это медленно для этого, потому что каждый раз, когда элемент добавляется, он вызывает события. Используйте что-то быстрее, например List<T>
, и поднимите уведомление об изменении свойств после добавления всех ваших элементов.
- Предварительно создайте свои элементы в фоновом потоке, а затем вставьте их в свою коллекцию.
- Проверьте другие части кода, чтобы увидеть, есть ли раздувание и обрезать.
Ответ 3
Еще одна вещь, которую вы можете попробовать: подкласс ObservableCollection и сделать его поддержкой массовой загрузки (AddRange). Вот статья:
AddRange и ObservableCollection
Ответ 4
По просьбе, вот как я решил эту проблему. Я начал с создания класса, который наследует от ObservableCollection
. Этот класс сделал две вещи - разоблачить метод для добавления целых коллекций сразу и добавил возможность подавлять событие CollectionChanged
. С этими изменениями время, затрачиваемое на добавление 3000 предметов, составляет примерно 0,4 секунды (улучшение на 97%). Эта ссылка описывает реализацию этих изменений.
Ответ 5
для второго вопроса
если в вашем графическом интерфейсе вы используете технологию WPF, вы можете увеличить производительность, используя VirualizingStackPanel, позволяющую создавать только видимые элементы