Ответ 1
TL; DR: исправлено. См. Нижнюю часть. Читайте дальше для моего путешествия открытия и всех неправильных переулков, я спустился!
Я сделал несколько попыток с этим, и я не думаю, что это утечка как таковая. Если я усилю GC, поместив эту сторону цикла в Images:
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Вы можете шагнуть (медленно) вниз по списку и не видеть никаких изменений в дескрипторах GDI через несколько секунд. В самом деле, проверка с MemoryProfiler подтверждает это - объекты .net или GDI утечки при медленном перемещении от элемента к элементу.
У вас проблемы с быстрым перемещением вниз по списку - я видел, что память процесса прошла мимо 1.5G, а объект GDI поднимался до 10000, когда он ударялся о стену. При каждом вызове MakeImage после этого была сброшена ошибка COM и ничего полезного для процесса не было:
A first chance exception of type 'System.Runtime.InteropServices.COMException' occurred in PresentationCore.dll
A first chance exception of type 'System.Runtime.InteropServices.COMException' occurred in PresentationCore.dll
A first chance exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll
System.Windows.Data Error: 8 : Cannot save value from target back to source. BindingExpression:Path=SelectedMasterItem; DataItem='MasterViewModel' (HashCode=28657291); target element is 'ListBox' (Name=''); target property is 'SelectedItem' (type 'Object') COMException:'System.Runtime.InteropServices.COMException (0x88980003): Exception from HRESULT: 0x88980003
at System.Windows.Media.Imaging.RenderTargetBitmap.FinalizeCreation()
Это, я думаю, объясняет, почему вы видите так много RenderTargetBitmaps, висящих вокруг. Он также предлагает мне стратегию смягчения последствий - если предположить, что это ошибка структуры /GDI. Попробуйте направить код визуализации (RenderImage) в домен, который позволит перезапустить базовый COM-компонент. Первоначально я попробовал бы нить в своей собственной квартире (SetApartmentState (ApartmentState.STA)), и если это не сработало, я бы попробовал AppDomain.
Однако было бы проще попытаться разобраться с источником проблемы, которая так быстро выделяет так много изображений, потому что даже если я получу ее до 9000 дескрипторов GDI и немного подожду, счет падает вернемся к базовой линии после следующего изменения (мне кажется, что в COM-объекте есть некоторая обработка бездействия, которая нуждается в нескольких секундах ничего, а затем другое изменение, чтобы выпустить все из них)
Я не думаю, что для этого есть какие-то легкие исправления. Я попытался добавить сон, чтобы замедлить движение и даже вызвать ComponentDispatched.RaiseIdle() - ни один из них не имеет никакого эффекта. Если бы мне пришлось работать так, я бы попытался запустить обработку GDI перезапустимым способом (и справиться с ошибками, которые могут произойти) или изменить пользовательский интерфейс.
В зависимости от требований в подробном представлении и, что самое важное, видимости и размера изображений в правой части, вы можете воспользоваться возможностью, чтобы ItemsControl виртуализовал ваш список (но, вероятно, вам придется наименьшее определение высоты и количества содержащихся изображений, чтобы он мог правильно управлять полосами прокрутки). Я предлагаю вернуть ObservableCollection изображений, а не IEnumerable.
Фактически, только что протестировав это, этот код, похоже, заставит проблему уйти:
public ObservableCollection<ImageSource> Images
{
get
{
return new ObservableCollection<ImageSource>(ImageSources);
}
}
IEnumerable<ImageSource> ImageSources
{
get
{
var random = new Random(seed);
for (int i = 0; i < 150; i++)
{
yield return MakeImage(random);
}
}
}
Главное, что дает время выполнения, насколько я вижу, это количество элементов (что, очевидно, нет, это означает, что он не должен перечислить его несколько раз или угадать (!)). Я могу бегать вверх и вниз по списку пальцем по клавише курсора без этих дующих ручек 10k, даже с 1000 MasterItems, так что это выглядит хорошо для меня. (Мой код также не имеет явного GC)