2012-01-27 4 views
10

마스터 - 세부 정보보기가있는 앱이 있습니다. '마스터'목록에서 항목을 선택하면 '세부 정보'영역에 RenderTargetBitmap을 통해 생성 된 일부 이미지가 채워집니다.RenderTargetBitmap 마스터 - 세부 정보보기의 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> 

목록에서 첫 번째 항목을 클릭 한 다음 아래쪽 커서 키를 누른 상태에서 문제를 재현 할 수 있습니다.

SOS를 사용하여 WinDbg에서 gcroot를 보는 것으로부터 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); 
     } 
    } 
} 

이 런타임을 제공합니다 중요한 것은, 지금까지의 내가 볼 수있는 항목의 수 (이다 그것은 열거 형, 분명히, 그렇지 않다)은 여러 번 열거하거나 추측하지 않아야한다는 것을 의미한다. 나는이 열혈 자없이 커서 키에 손가락을 대고 목록을 위아래로 움직일 수 있습니다. 심지어 1000 MasterItem이 있어도 잘 보입니다. (내 코드에는 명시적인 GC가 없습니다.)

+0

ObservableCollection도 캐싱하려고했습니다. 불행하게도 컬렉션을 가지고 있으면 궁극적으로 GDI 핸들도 보유하게 될 것입니다. –

+0

고맙습니다. 그것은 샘플 애플 리케이션을 위해 그것을 해결하지, 난 그냥 실제 애플 리케이션에 피팅을 시도해야합니다. ObservableCollection이 왜 여기서 도움이되는지 나는 잘 모르겠습니다. 크기 때문인 경우 목록 에 동일한 효과가 있어야합니다. – Wilka

2

더 간단한 비트 맵 유형으로 복제 (및 고정)하면 많은 gdi 핸들을 사용하지 않지만 느려집니다. 응답에 직렬화를 통한 복제가 있습니다. How achieve Image.Clone() in WPF?"

+0

WriteableBitmap에는 BitmapSource를 사용하는 ctor가 있으므로 복제하는 것이 더 빠르며 문제가 해결됩니다. 감사. – Wilka