2012-01-27 31 views
10

Tôi có ứng dụng có chế độ xem Chi tiết chính. Khi bạn chọn một mục từ danh sách 'chính', nó sẽ điền khu vực 'chi tiết' với một số hình ảnh (được tạo thông qua RenderTargetBitmap).RenderTargetBitmap GDI xử lý rò rỉ trong chế độ xem Chi tiết chính

Mỗi khi tôi chọn một mục chính khác từ danh sách, số lượng xử lý GDI được sử dụng bởi ứng dụng của tôi (như được báo cáo trong Process Explorer) tăng lên - và cuối cùng rơi xuống (hoặc đôi khi khóa) tại 10.000 GDI đang sử dụng.

Tôi đang thua lỗ về cách khắc phục điều này, vì vậy mọi đề xuất về những gì tôi đang làm sai (hoặc chỉ đề xuất cách nhận thêm thông tin) sẽ được đánh giá cao.

tôi đã đơn giản hóa ứng dụng của tôi xuống đến sau trong một ứng dụng mới WPF (.NET 4.0) được gọi là "DoesThisLeak":

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

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

Bạn có thể tạo lại sự cố nếu bạn nhấp vào mục đầu tiên trong danh sách, sau đó nhấn và giữ phím Con trỏ.

Từ khi nhìn vào! Gcroot trong WinDbg với SOS, tôi không thể tìm thấy bất cứ điều gì giữ cho các đối tượng RenderTargetBitmap còn sống, nhưng nếu tôi làm !dumpheap -type System.Windows.Media.Imaging.RenderTargetBitmap nó vẫn hiển thị một vài nghìn trong số chúng chưa được thu thập.

Trả lời

7

TL; DR: cố định. Xem đáy. Đọc tiếp cho hành trình khám phá của tôi và tất cả những con hẻm sai lầm tôi đã đi xuống!

Tôi đã thực hiện một số xung quanh với điều này, và tôi không nghĩ rằng nó bị rò rỉ như vậy. Nếu tôi tăng cường GC bằng cách đặt một trong hai bên của vòng lặp này vào Hình ảnh:

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

Bạn có thể bước (từ từ) xuống danh sách và không thấy thay đổi trong xử lý GDI sau vài giây. Thật vậy, việc kiểm tra với MemoryProfiler xác nhận điều này - không có đối tượng .net hoặc GDI bị rò rỉ khi di chuyển chậm từ mục này sang mục khác.

Bạn gặp rắc rối khi di chuyển nhanh xuống danh sách - Tôi thấy tiêu đề bộ nhớ quá trình qua 1.5G và đối tượng GDI leo lên 10000 khi nó chạm vào tường. Mỗi lần MakeImage được gọi sau đó, một lỗi COM đã được ném và không có gì hữu ích có thể được thực hiện cho quá trình này:

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

này, tôi nghĩ rằng giải thích lý do tại sao bạn thấy rất nhiều RenderTargetBitmaps treo xung quanh. Nó cũng gợi ý cho tôi một chiến lược giảm thiểu - giả sử đó là một lỗi khung/GDI. Hãy thử để đẩy mã render (RenderImage) vào một tên miền mà sẽ cho phép các thành phần COM cơ bản được khởi động lại. Ban đầu, tôi muốn thử một thread trong căn hộ riêng của nó (SetApartmentState (ApartmentState.STA)) và nếu điều đó không làm việc, tôi sẽ thử một AppDomain.

Tuy nhiên, sẽ dễ dàng hơn khi cố gắng giải quyết vấn đề, phân bổ quá nhiều hình ảnh quá nhanh, bởi vì ngay cả khi tôi nhận được tới 9000 GDI và chờ một chút, số lượng giảm phải quay trở lại đường cơ sở sau khi thay đổi tiếp theo (có vẻ như tôi có một số xử lý nhàn rỗi trong đối tượng COM cần vài giây không có gì, và sau đó thay đổi khác để giải phóng tất cả các xử lý của nó)

I don ' t nghĩ rằng có bất kỳ sửa chữa dễ dàng cho điều này - Tôi đã cố gắng thêm một giấc ngủ để làm chậm chuyển động xuống, và thậm chí gọi ComponentDispatched.RaiseIdle() - không phải trong số này có bất kỳ tác dụng. Nếu tôi phải làm cho nó hoạt động theo cách này, tôi sẽ cố gắng chạy xử lý GDI theo cách có thể khởi động lại (và xử lý các lỗi xảy ra) hoặc thay đổi giao diện người dùng.

Tùy thuộc vào yêu cầu trong chế độ xem chi tiết và quan trọng nhất là khả năng hiển thị và kích thước của hình ảnh ở bên phải, bạn có thể tận dụng khả năng của ItemsControl để ảo hóa danh sách của bạn (nhưng có thể bạn phải ít nhất là xác định chiều cao và số lượng hình ảnh được chứa để nó có thể quản lý thanh cuộn đúng cách). Tôi đề nghị trả lại một ObservableCollection của hình ảnh, chứ không phải là một IEnumerable.

Trong thực tế, vừa thử nghiệm đó, mã này dường như làm cho vấn đề đi xa:

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

Điều quan trọng này cho phép thời gian chạy, như xa như tôi có thể thấy, là số lượng các mục (trong đó có thể đếm được, hiển nhiên, không có nghĩa là nó không phải liệt kê nó nhiều lần, hoặc đoán (!). Tôi có thể chạy lên và xuống danh sách bằng ngón tay của tôi trên phím con trỏ mà không cần xử lý 10k thổi này, ngay cả với 1000 MasterItems, vì vậy nó có vẻ tốt với tôi. (Mã của tôi không có GC rõ ràng)

+0

Lưu ý, tôi cũng đã cố gắng lưu vào bộ nhớ cache ObservableCollection. Thật không may, việc giữ bộ sưu tập dường như cuối cùng cũng giữ được các xử lý GDI. –

+0

Cảm ơn, điều đó thật tuyệt. Nó sửa chữa nó cho các ứng dụng mẫu, tôi chỉ cần cố gắng phù hợp mà vào các ứng dụng thực tế bây giờ. Tôi không chắc chắn lý do tại sao ObservableCollection giúp ở đây mặc dù. Nếu chỉ vì kích thước, thì Danh sách sẽ có cùng tác dụng. – Wilka

2

Nếu bạn sao chép thành một loại bitmap đơn giản hơn (và cố định), nó sẽ không sử dụng nhiều điều khiển gdi, nhưng nó sẽ chậm hơn. Có nhân bản qua serialization trong câu trả lời cho How achieve Image.Clone() in WPF?"

+0

WriteableBitmap có một ctor có một BitmapSource, do đó nhân bản nó thành nhanh hơn và nó cũng khắc phục được sự cố. Cảm ơn. – Wilka

Các vấn đề liên quan