TL;DR:已修复。见底部。请继续阅读我的发现之旅以及我走过的所有错误小巷!
我已经对此进行了一些探索,但我不认为它会泄漏。如果我通过将其放在图像循环的任一侧来增强 GC:
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 处理(并处理可能发生的错误)或更改 UI。
根据详细视图中的要求,最重要的是,右侧图像的可见性和大小,您可以利用 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);
}
}
}
据我所知,这给运行时带来的主要影响是项目的数量(显然,可枚举的项目数量不是),这意味着它既不需要多次枚举它,也不需要猜测(!)。即使有 1000 个 MasterItem,我也可以用手指在光标键上在列表中上下移动,而无需吹响 10k 句柄,所以它对我来说看起来不错。 (我的代码也没有显式GC)