2012-01-27 3 views
10

私はMaster-Detailsビューを持つアプリケーションを持っています。 「マスター」リストから項目を選択すると、「詳細」領域にいくつかの画像(RenderTargetBitmapで作成)が設定されます。RenderTargetBitmap Master-DetailsビューのGDIハンドルリーク

私は、リストから別のマスターアイテムを選択するたびに、(プロセスエクスプローラで報告されたように)自分のアプリケーションで使用されているGDIハンドルの数が増え、最終的に10,000個のGDIハンドルで落ちる使用中で。

私はこれを修正する方法を失っているので、私が間違っていることについての提案(または、より多くの情報を得る方法に関する提案)は非常に高く評価されます。

私がダウンして、 "DoesThisLeak" と呼ばれる新しいWPFアプリケーション(.NET 4.0)に次のように私のアプリを簡略化してきました

MainWindow.xaml.cs MainWindow.xamlで

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; 
    } 
} 

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

リストの最初の項目をクリックし、次にダウンカーソルキーを押したままにすると、問題を再現できます。

WinDbgの!gcrootをSOSで見ると、これらのRenderTargetBitmapオブジェクトを維持しているものが見つかりませんでしたが、私が!dumpheap -type System.Windows.Media.Imaging.RenderTargetBitmapを実行してもまだ収集されていない数千ものものが表示されます。

答えて

7

TL; DR:固定。下を見てください。私の旅の旅と、私が降りたすべての間違った路地を読んでください!

これでいくつかのことを掘り下げましたが、そのように漏れているとは思われません。私は画像でループのこのいずれかの側に置くことによってGCをアップ牛肉場合:

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

あなたは(ゆっくり)リストステップダウンすることができますし、GDIは数秒後にハンドルに何の変化も見られません。 確かに、MemoryProfilerで確認すると、項目間でゆっくりと移動するときに.netやGDIオブジェクトがリークすることはありません。

リストをすばやく動かすには問題があります。プロセスメモリが1.5Gを超え、GDIオブジェクトが壁に衝突したときに10000に登るのがわかりました。 MakeImageがその後に呼び出されたたびに、COMエラーがスローされた、有用なものは何もプロセスのために行われないことができます:あなたの周りぶら下がっ非常に多くのRenderTargetBitmapsを参照してください理由

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() 

これ、私が考えて説明しています。また、フレームワーク/ GDIバグだと仮定して、軽減戦略を私に提案します。レンダリングコード(RenderImage)を基になるCOMコンポーネントを再起動できるようにするドメインにプッシュしてみてください。最初は、自分のアパート(SetApartmentState(ApartmentState.STA))でスレッドを試してみましたが、それがうまくいかない場合はAppDomainを試してみました。

しかし、非常に多くのイメージをすばやく割り当てている問題の原因を処理する方が簡単です。なぜなら、最大9000個のGDIハンドルを取得して少し待ってもカウントが低下するからです次の変更後にベースラインまで戻ってきます(COMオブジェクトに何らかのアイドル処理があり、数秒間何も処理が必要でないと思われます)。

I don '私は動きを遅くするために睡眠を追加しようとしました、そしてComponentDispatched.RaiseIdle()を呼び出すことさえ - これらはどちらも効果がありません。このようにしなければならない場合は、GDI処理を再開可能な方法で実行し、発生するエラーに対処するか、UIを変更しようとしています。

詳細ビューの要件に加えて、最も重要なことは、右側の画像の表示とサイズによって、ItemsControlがリストを仮想化できることです少なくともスクロールバーを適切に管理できるように、含まれている画像の高さと数を定義する必要があります)。 IEnumerableではなく、ObservableCollectionイメージを返すことをお勧めします。ただ、このコードは問題が離れて行くように見えることをテストした事実

、:

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); 
     } 
    } 
} 

これはランタイムを与える主なものは、私の知る限り、項目の数(ありますenumerableは明らかにそうではありません)、それはそれを複数回列挙したり、推測する必要はありません(!)。私は1000キロのMasterItemでさえ、この吹き飛ばす10kハンドルなしで、カーソルキーの上で指でリストを上下に動かすことができます。 (私のコードには明示的なGCもありません)

+0

ObservableCollectionもキャッシュしようとしました。残念なことに、コレクションを保持することは最終的にGDIのハンドルも保持しているようです。 –

+0

ありがとう、それは素晴らしいです。それは、サンプルアプリケーションのためにそれを修正します、私はちょうど実際のアプリにそれをフィッティングしようとする必要があります。なぜObservableCollectionがここで役立つのか分かりません。それがちょうどサイズのためだったら、リストは同じ効果を持っているはずです。 – Wilka

2

より簡単なビットマップタイプにクローン化して(フリーズする)、多くのgdiハンドルを使いませんが、速度は遅くなります。 答えにシリアル化によるクローンがありますHow achieve Image.Clone() in WPF?"

+0

WriteableBitmapにはBitmapSourceを使用するctorがあります。そのため、その中にクローンを入れるほうが速く、問題も解決します。ありがとう。 – Wilka

関連する問題