В моей форме есть два элемента управления: список со списком работников и панель, которая действует как контейнер для отображения сведений (карточек) об их работе. Когда пользователь нажимает на имя работника, я отображаю карточки на панели. Карта — это пользовательский элемент управления с довольно простым пользовательским интерфейсом (2 групповых поля, 3 текстовых поля и несколько меток) и простой логикой (настройка переднего цвета меток).
Карты создаются во время выполнения. Предыдущие карты убираются с панели и добавляются новые - количество карт на одного рабочего от 1 до 4. Тут становится интересно. Все работает нормально до ок. пятый щелчок по рабочим. Кажется, что GC срабатывает, и требуется около двух секунд (0,3 с x количество ранее удаленных карт) для удаления старых карт (ранее удаленных) и отображения новых. Если раньше перемещение между работниками работало отлично, то в этот момент оно становится болезненно медленным. После некоторого изучения я обнаружил, что проблема заключается в методе Dispose
моего используемого элемента управления. Вызов base.Dispose()
занимает около 0,3 с.
Вот мой код:
private void ShowCards(List<Work> workItems) {
var y = 5;
panelControl1.SuspendLayout();
panelControl1.Controls.Clear();
foreach (var work in workItems) {
var card = new Components.WorkDisplayControl(work);
card.Top = y;
card.Left = 10;
y += card.Height + 5;
panelControl1.Controls.Add(card);
}
panelControl1.ResumeLayout(true);
Application.DoEvents();
}
Что я пробовал до сих пор:
- скрытие карт вместо выбрасывания - работает быстрее при перемещении между воркерами, но штраф оплачивается при закрытии формы
- скрыть карты и иметь отдельный поток, который их распоряжается - без изменений
- тест с добавлением 10 карт и их немедленной утилизацией - медленный
- тест с добавлением 10 карт и размещением их сразу в конструкторе - БЫСТРО!
- заменены элементы управления DevExpress на «нормальные» — без изменений
- ручное удаление старых карт вместо их удаления при смене воркера - каждый переход между воркерами становится медленнее:
for (var ix = panelControl1.Controls.Count - 1; ix >= 0; --ix) { panelControl1.Controls[ix].Dispose();}
- профилировать - вот как я нашел проблему в
Dispose
. Я могу проследить это доControl.DestroyHandle
- вызов
Controls.Clear()
вDispose
методе моего управления - супер странное поведение и исключения - удалил все элементы управления из моего пользовательского контроля - немного быстрее, но все же медленно
- скрытие
panelControl1
при удалении и добавлении карт - без изменений - отключил фоновый GC - без изменений
- добавление карт с
AddRange
Поскольку та же функциональность работает быстро при вызове из конструктора, я считаю, что причина должна быть где-то в (управляющих) дескрипторах.
Я просто не могу найти причину такого странного поведения. Буду признателен за любые идеи....
ОБНОВЛЕНИЕ: при исследовании связи между GC и Control.Dispose я нашел отличный ответ