RenderTargetBitmap GDI处理“主-详细信息"视图中的泄漏 [英] RenderTargetBitmap GDI handle leak in Master-Details view

查看:119
本文介绍了RenderTargetBitmap GDI处理“主-详细信息"视图中的泄漏的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个具有Master-Details视图的应用程序.当您从主"列表中选择一个项目时,它将在详细信息"区域中填充一些图像(通过RenderTargetBitmap创建).

I have an app with a Master-Details view. When you select an item from the 'master' list, it populates the 'details' area with some images (created via RenderTargetBitmap).

每次我从列表中选择一个不同的主项目时,我的应用程序正在使用的GDI句柄数量(在Process Explorer中报告)会增加-最终跌落(或有时被锁定)为10,000个GDI句柄使用.

Each time I select a different master item from the list, the number of GDI handles in use by my app (as reported in Process Explorer) goes up - and eventually falls over (or sometimes locks up) at 10,000 GDI handles in use.

我对如何解决此问题不知所措,因此,如果有任何关于我做错事情的建议(或者只是有关如何获取更多信息的建议),将不胜感激.

I'm at a loss on how to fix this, so any suggestions on what I'm doing wrong (or just suggestions on how to get more information) would be greatly appreciated.

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

I've simplified my app down to the following in a new WPF Application (.NET 4.0) called "DoesThisLeak":

在MainWindow.xaml.cs

In 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

In 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>

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

You can reproduce the problem if you click on the first item in the list, then hold down the Down cursor key.

通过带有SOS的WinDbg中的!gcroot,我找不到使这些RenderTargetBitmap对象保持活动状态的任何东西,但是如果我这样做,它仍然显示数千个尚未被收集的对象.

From looking at !gcroot in WinDbg with SOS, I can't find anything keeping those RenderTargetBitmap objects alive, but if I do !dumpheap -type System.Windows.Media.Imaging.RenderTargetBitmap it still shows a few thousand of them that haven't been collected yet.

推荐答案

TL; DR:已修复.见底部.继续阅读,了解我的发现之旅以及我走下的所有错误小巷!

TL;DR: fixed. See the bottom. Read on for my journey of discovery and all the wrong alleys I went down!

我已经对此进行了一些探索,但我认为它并没有泄漏出来.如果我通过将循环的这一边放在图像"中来增强GC的功能,则:

I've done some poking around with this, and I don't think it's leaking as such. If I beef up the GC by putting this either side of the loop in Images:

GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();

几秒钟后,您可以(缓慢地)移至列表的下方,并且GDI手柄中没有任何变化. 确实,通过MemoryProfiler进行检查可以确认这一点-当逐项缓慢移动时,.net或GDI对象不会泄漏.

You can step (slowly) down the list and see no change in the GDI handles after a few seconds. Indeed, checking with MemoryProfiler confirms this - no .net or GDI objects leak when moving slowly from item to item.

在列表中快速向下移动确实遇到了麻烦-我看到进程内存超过1.5G,当GDI对象碰到墙时,它攀升到10000.此后每次调用MakeImage时,都会引发COM错误,并且对该过程无法做任何有用的事情:

You do get into trouble moving quickly down the list - I saw process memory heading past 1.5G and the GDI object climbing to 10000 when it hit a wall. Every time MakeImage was called after that, a COM error was thrown and nothing useful could be done for the process:

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.

This, I think explains why you see so many RenderTargetBitmaps hanging around. It also suggests to me a mitigation strategy - assuming it's a framework/GDI bug. Try to push the render code (RenderImage) into a domain which will allow the underlying COM component to be restarted. Initially, I'd try a thread in it's own apartment (SetApartmentState(ApartmentState.STA)) and if that didn't work, I'd try an AppDomain.

但是,尝试解决问题的根源会比较容易,因为这么快就分配了这么多图像,因为即使我将其分配到9000个GDI句柄并稍等片刻,计数仍然正确在下一次更改后回到基线(在我看来,COM对象中有一些空闲处理,它需要几秒钟的时间,然后进行另一次更改以释放其所有句柄)

However, it'd be easier to try to deal with the source of the problem, which is allocating so many images so quickly, because even if I get it up to 9000 GDI handles and wait a bit, the count falls right back down to the baseline after the next change (it seems to me as there's some idle processing in the COM object which needs a few seconds of nothing, and then another change to release all of it's handles)

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

I don't think there are any easy fixes for this - I've tried adding a sleep to slow the movement down, and even calling ComponentDispatched.RaiseIdle() - neither of these have any effect. If I had to make it work this way, I'd be trying to run the GDI processing in a restartable way (and dealing with the errors which would occur) or changing the UI.

取决于详细视图中的要求,最重要的是,取决于右侧图像的可见性和大小,您可以利用ItemsControl的功能来虚拟化列表(但您可能必须在至少定义所包含图像的高度和数量,以便它可以正确管理滚动条).我建议返回图像的ObservableCollection,而不是IEnumerable.

Depending on the requirements in the detail view, and most importantly, the visibility and size of the images in the right hand side, you could take advantage of the ability of the ItemsControl to virtualise your list (but you probably have to at least define the height and number of the contained images so it can manage the scrollbars properly). I suggest returning an ObservableCollection of images, rather than an IEnumerable.

实际上,经过测试,此代码似乎可以使问题消失:

In fact, having just tested that, this code appears to make the problem go away:

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个MasterItem,也对我来说看起来不错. (我的代码也没有显式GC)

The main thing this gives the runtime, as far as I can see, is the number of items (which the enumerable, obviously, does not) meaning that it neither has to enumerate it multiple times, or guess (!). I can run up and down the list with my finger on the cursor key without this blowing 10k handles, even with 1000 MasterItems, so it looks good to me. (My code has no explicit GC either)

这篇关于RenderTargetBitmap GDI处理“主-详细信息"视图中的泄漏的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆