gpt4 book ai didi

c# - Master-Details View 中的 RenderTargetBitmap GDI 句柄泄漏

转载 作者:可可西里 更新时间:2023-11-01 08:26:03 25 4
gpt4 key购买 nike

我有一个带有主从 View 的应用程序。当您从“主”列表中选择一个项目时,它会使用一些图像(通过 RenderTargetBitmap 创建)填充“详细信息”区域。

每次我从列表中选择不同的主项时,我的应用程序使用的 GDI 句柄数量(如 Process Explorer 中所报告)都会增加 - 并最终下降(或有时锁定)在 10,000 个 GDI 句柄处使用。

我不知道如何解决这个问题,因此非常感谢任何关于我做错了什么的建议(或者只是关于如何获取更多信息的建议)。

我在名为“DoesThisLeak”的新 WPF 应用程序 (.NET 4.0) 中将我的应用程序简化为以下内容:

在 MainWindow.xaml.cs 中

public partial class MainWindow : Window
{
public MainWindow()
{
ViewModel = new MasterViewModel();
InitializeComponent();
}

public MasterViewModel ViewModel { get; set; }
}

public class MasterViewModel : INotifyPropertyChanged
{
private MasterItem selectedMasterItem;

public IEnumerable<MasterItem> MasterItems
{
get
{
for (int i = 0; i < 100; i++)
{
yield return new MasterItem(i);
}
}
}

public MasterItem SelectedMasterItem
{
get { return selectedMasterItem; }
set
{
if (selectedMasterItem != value)
{
selectedMasterItem = value;

if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("SelectedMasterItem"));
}
}
}
}

public event PropertyChangedEventHandler PropertyChanged;
}

public class MasterItem
{
private readonly int seed;

public MasterItem(int seed)
{
this.seed = seed;
}

public IEnumerable<ImageSource> Images
{
get
{
GC.Collect(); // Make sure it's not the lack of collections causing the problem

var random = new Random(seed);

for (int i = 0; i < 150; i++)
{
yield return MakeImage(random);
}
}
}

private ImageSource MakeImage(Random random)
{
const int size = 180;
var drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawRectangle(Brushes.Red, null, new Rect(random.NextDouble() * size, random.NextDouble() * size, random.NextDouble() * size, random.NextDouble() * size));
}

var bitmap = new RenderTargetBitmap(size, size, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(drawingVisual);
bitmap.Freeze();
return bitmap;
}
}

在 MainWindow.xaml 中

<Window x:Class="DoesThisLeak.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="900" Width="1100"
x:Name="self">
<Grid DataContext="{Binding ElementName=self, Path=ViewModel}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="210"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Grid.Column="0" ItemsSource="{Binding MasterItems}" SelectedItem="{Binding SelectedMasterItem}"/>

<ItemsControl Grid.Column="1" ItemsSource="{Binding Path=SelectedMasterItem.Images}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Window>

如果单击列表中的第一项,然后按住向下光标键,则可以重现该问题。

通过使用 SOS 查看 WinDbg 中的 !gcroot,我找不到任何让那些 RenderTargetBitmap 对象保持事件状态的东西,但是如果我这样做 !dumpheap -type System.Windows.Media.Imaging.RenderTargetBitmap 它仍然显示其中几千个尚未收集。

最佳答案

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 对象中有一些空闲处理需要几秒钟的时间,然后进行另一次更改以释放所有句柄)

我认为对此没有任何简单的修复方法 - 我尝试添加 sleep 以减慢移动速度,甚至调用 ComponentDispatched.RaiseIdle() - 这些都没有任何效果。如果我必须让它以这种方式工作,我将尝试以可重新启动的方式运行 GDI 处理(并处理可能发生的错误)或更改 UI。

根据详细 View 中的要求,最重要的是,右侧图像的可见性和大小,您可以利用 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 个 MasterItems,我也可以用我的手指在光标键上在列表中上下移动而不会吹出 10k handle ,所以它对我来说看起来不错。 (我的代码也没有明确的 GC)

关于c# - Master-Details View 中的 RenderTargetBitmap GDI 句柄泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9034362/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com