2012-01-27 22 views
10

Mam aplikację z widokiem szczegółów szczegółowych. Po wybraniu elementu z listy "master" wypełni obszar "szczegóły" obrazami (utworzonymi za pomocą RenderTargetBitmap).RenderTargetBitmap Przeciek uchwytu GDI w widoku szczegółów szczegółowych

Za każdym razem, gdy wybieram inny element główny z listy, liczba uchwytów GDI używanych przez moją aplikację (zgodnie z raportem w Eksploratorze procesów) wzrasta - i ostatecznie spada (lub czasami blokuje się) przy 10 000 uchwytach GDI w użyciu.

Nie mam pojęcia, jak to naprawić, więc wszelkie sugestie dotyczące tego, co robię źle (lub tylko sugestie, jak uzyskać więcej informacji) byłyby bardzo mile widziane.

Mam uproszczone mojej aplikacji w dół do następujących w nowej aplikacji WPF (.NET 4.0) o nazwie "DoesThisLeak":

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

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

Możesz odtworzyć problem, klikając pierwszy element na liście, a następnie przytrzymując klawisz kursora w dół.

Spoglądając na! Gcroot w WinDbg z SOS, nie mogę znaleźć niczego, co utrzyma przy życiu te obiekty RenderTargetBitmap, ale jeśli wykonam !dumpheap -type System.Windows.Media.Imaging.RenderTargetBitmap, to nadal pokazuje kilka tysięcy z nich, które jeszcze nie zostały zebrane.

Odpowiedz

7

TL; DR: poprawiony. Zobacz dno. Czytaj dalej moją podróż odkrywczą i wszystkie złe uliczki, które zeszedłem!

Zrobiłem kilka szturchanie z tym, i nie sądzę, że to przeciek jako takie. Gdybym rozbudować GC poprzez umieszczenie tego po obu stronach pętli w obrazach:

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

Możesz przejść (powoli) na dół listy i nie widzę zmiany w GDI uchwyty po kilku sekundach. Rzeczywiście, sprawdzanie za pomocą narzędzia MemoryProfiler potwierdza to - żadne obiekty .net lub GDI nie wyciekają podczas przesuwania się powoli z elementu na pozycję.

Wpadasz w kłopoty, szybko przesuwając się w dół listy - widziałem pamięć procesu przechodzącą obok 1.5G, a obiekt GDI wspiął się na 10000, kiedy uderzył w ścianę. Za każdym razem MakeImage nazwano potem błąd COM został wyrzucony i nic nie można zrobić użyteczny dla procesu:

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

To, jak sądzę, wyjaśnia dlaczego tak wiele RenderTargetBitmaps kręci. Sugeruje mi to również strategię łagodzenia - zakładając, że jest to błąd związany z ramą/GDI. Spróbuj przekazać kod renderowania (RenderImage) do domeny, która pozwoli zrestartować podstawowy składnik COM. Początkowo wypróbowałem wątek w jego własnym mieszkaniu (SetApartmentState (ApartmentState.STA)) i gdyby to nie zadziałało, spróbowałbym AppDomain.

Jednak łatwiej byłoby spróbować poradzić sobie ze źródłem problemu, który polega na tak szybkim przydzielaniu tylu obrazów, ponieważ nawet jeśli dostanę do 9000 uchwytów GDI i trochę poczekać, liczba spada wracając do linii bazowej po następnej zmianie (wydaje mi się, że w obiekcie COM jest trochę bezczynnego przetwarzania, które potrzebuje kilku sekund na nic, a następnie kolejna zmiana w celu zwolnienia wszystkich jego uchwytów)

I don ' Sądzę, że są jakieś proste poprawki - próbowałem dodać sen, aby spowolnić ruch, a nawet wywołać ComponentDispatched.RaiseIdle() - żadne z nich nie ma żadnego efektu. Gdybym musiał sprawić, żeby działało w ten sposób, chciałbym uruchomić przetwarzanie GDI w sposób umożliwiający ponowne uruchomienie (i radzenie sobie z błędami, które wystąpiłyby) lub zmianę interfejsu użytkownika.

W zależności od wymagań w widoku szczegółowym, a co najważniejsze, widoczności i rozmiaru obrazów po prawej stronie, możesz skorzystać z możliwości ItemControl do zmyślenia listy (ale prawdopodobnie musisz przynajmniej określić wysokość i liczbę zawartych obrazów, aby móc poprawnie obsługiwać paski przewijania). Proponuję zwrócić ObservableCollection obrazów, a nie IEnumerable.

W rzeczywistości, po prostu przetestowane, że ten kod pojawia się, aby problem znika:

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

Najważniejsze, to daje czas pracy, o ile widzę, to liczba elementów (które oczywiście wyliczalne nie ma), co oznacza, że ​​ani nie musi go wyliczać wiele razy, ani odgadnąć (!). Mogę poruszać się po liście palcami po klawiszu kursora bez tego dmuchania 10-krotnym uchwytem, ​​nawet z 1000 MasterItems, więc wygląda mi dobrze. (Mój kod również nie ma wyraźnego GC)

+0

Uwaga, próbowałem również buforować ObservableCollection. Niestety, przechowywanie kolekcji wydaje się ostatecznie utrzymywać obsługę GDI. –

+0

Dzięki, to świetnie. Naprawia to dla przykładowej aplikacji, po prostu muszę spróbować dopasować ją do prawdziwej aplikacji. Nie jestem pewien, dlaczego ObservableCollection pomaga tutaj. Jeśli był to tylko rozmiar, lista powinna mieć taki sam efekt. – Wilka

2

Jeśli klonujesz w prostszy typ bitmapy (i zamarzasz), nie użyjesz tylu uchwytów gdi, ale będzie wolniej. Istnieje klonowanie przez serializację w odpowiedzi na How achieve Image.Clone() in WPF?"

+0

WriteableBitmap ma ctor, który pobiera BitmapSource, więc klonowanie go jest szybsze i rozwiązuje problem. Dzięki. – Wilka