2012-02-21 30 views
9

Vì vậy, tôi đang sử dụng WPF 3.5 với phương pháp MVVM + DataTemplate để tải 2 chế độ xem trên GUI. Tôi đã quan sát thấy trong khi bộ nhớ định hình rằng các mục được tạo ra như một phần của các thùng chứa các mục điều khiển được gắn vào bộ nhớ và không nhận được GC ngay cả sau khi xem được dỡ bỏ!Trường hợp được ghim cho GC - Không thể theo dõi từ mã được quản lý của tôi

Tôi vừa chạy thử nghiệm và phát hiện ra nó có thể tái sản xuất ngay cả đối với mã đơn giản nhất ... Các bạn có thể tự kiểm tra.

XAML:

<Window x:Class="ContentControlVMTest.Window2" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     xmlns:local="clr-namespace:ContentControlVMTest" 
     Title="Window2" Height="300" Width="300"> 
    <DockPanel LastChildFill="True"> 

     <CheckBox Click="CheckBox_Click" Content="Test1?" 
        DockPanel.Dock="Top" Margin="5"/> 

     <ContentControl x:Name="contentControl"> 
      <ContentControl.Resources> 

       <DataTemplate DataType="{x:Type local:Test3}"> 
        <TextBlock Text="{Binding C}" Margin="5"/> 
       </DataTemplate> 

       <DataTemplate DataType="{x:Type local:Test1}"> 
        <DockPanel LastChildFill="True" Margin="5"> 
         <TextBlock Text="{Binding A}" 
            DockPanel.Dock="Top" 
            Margin="5"/> 
         <ListBox ItemsSource="{Binding Bs}" 
           DisplayMemberPath="B" 
           Margin="5"/> 
        </DockPanel> 
       </DataTemplate> 
      </ContentControl.Resources> 
     </ContentControl> 
    </DockPanel> 
</Window> 

Mã Đằng sau:

public class Test3 
{ 
    public string C { get; set; } 
} 

public class Test2 
{ 
    public string B { get; set; } 
} 

public class Test1 
{ 
    public string A { get; set; } 

    private List<Test2> _Bs; 
    public List<Test2> Bs 
    { 
     get 
     { 
      return _Bs; 
     } 

     set 
     { 
      _Bs = value; 
     } 
    } 
} 

public partial class Window2 : Window 
{ 
    public Window2() 
    { 
     InitializeComponent(); 
     this.KeyDown += Window_KeyDown; 
    } 

    private void Window_KeyDown 
      (object sender, System.Windows.Input.KeyEventArgs e) 
    { 
     if (Keyboard.IsKeyDown(Key.LeftCtrl)) 
      if (Keyboard.IsKeyDown(Key.LeftShift)) 
       if (Keyboard.IsKeyDown(Key.LeftAlt)) 
        if (Keyboard.IsKeyDown(Key.G)) 
        { 
         GC.Collect(2, GCCollectionMode.Forced); 
         GC.WaitForPendingFinalizers(); 
         GC.Collect(2, GCCollectionMode.Forced); 
         GC.WaitForPendingFinalizers(); 
         GC.Collect(3, GCCollectionMode.Forced); 
         GC.WaitForPendingFinalizers(); 
         GC.Collect(3, GCCollectionMode.Forced); 
        } 
    } 

    private void CheckBox_Click(object sender, RoutedEventArgs e) 
    { 
     if (((CheckBox)sender).IsChecked.GetValueOrDefault(false)) 
     { 
      var x = new Test1() { A = "Test1 A" }; 
      x.Bs = new List<Test2>(); 
      for (int i = 1; i < 10000; i++) 
      { 
       x.Bs.Add(new Test2() { B = "Test1 B " + i }); 
      } 
      contentControl.Content = x; 
     } 
     else 
     { 
      contentControl.Content = new Test3() { C = "Test3 C" }; 
     } 
    } 
} 

tôi thực hiện buộc GC bằng phím Shift trái + Alt + Ctrl + G. Tất cả các mục cho xem Test1 hoặc Test3 và Xem mẫu bị chết sau khi chúng được tải xuống một cách chính xác. Vì vậy, đó là như mong đợi.

Nhưng bộ sưu tập được tạo trong mô hình Test1 (có Test2 đối tượng), vẫn được ghim vào bộ nhớ. Và nó chỉ ra rằng mảng là mảng được sử dụng bởi các thùng chứa các mục của hộp danh sách bởi vì nó cho thấy số lượng các mục bị ảo hóa khỏi hộp danh sách! Mảng được ghim này thay đổi kích thước của nó khi chúng tôi thu nhỏ hoặc khôi phục chế độ xem ở chế độ xem Test1! Một lần nó là 16 bài và lần sau là 69 mục khi được lược tả.

enter image description here

Điều này có nghĩa WPF thực hiện ghim các hạng mục được tạo ra trong mục kiểm soát! Bất cứ ai có thể giải thích điều này? Điều này có bất kỳ hạn chế đáng kể nào không?

Thx rất nhiều.

+0

Có lẽ một 'CollectionView' tạo ra cho bộ sưu tập được treo xung quanh. – user7116

+0

thx cho phản hồi. Vâng! Đây là bộ sưu tập các vật phẩm từ vật phẩm chứa. Nhưng tại sao sẽ là treo xung quanh? Chế độ xem đã biến mất. ListBox đã biến mất. Tại sao WPF sẽ thu thập các bộ sưu tập trong memeory? –

+0

Bạn có thể đăng gốc cho ví dụ đã ghim không. –

Trả lời

3

Sự cố là do cơ chế ràng buộc phát hành đầy đủ các mục danh sách đã thực sự bị ràng buộc để hiển thị trên màn hình. Đó là bit cuối cùng là gần như chắc chắn lý do tại sao bạn đang nhìn thấy số lượng khác nhau của "mồ côi" trường hợp trong chạy khác nhau. Bạn càng cuộn danh sách càng nhiều thì bạn càng gặp nhiều vấn đề.

Điều này dường như liên quan đến cùng một loại vấn đề cơ bản như được mô tả trong a bug report that I submitted over a year ago vì gốc cây ghim và các cây mẫu được ghim tương tự nhau. (Để xem loại chi tiết đó ở định dạng thuận tiện, bạn có thể muốn lấy một bản sao của một trình thu thập bộ nhớ hơi phồn vinh như ANTS Memory Profiler.)

Tin xấu thực sự là các trường hợp mồ côi của bạn đang bị ghim qua sự sụp đổ của cửa sổ chính nó, vì vậy bạn có lẽ không thể làm sạch chúng lên mà không có cùng một loại hack tôi đã phải sử dụng trong kịch bản WinForms để buộc clean-up của các privates ràng buộc.

Chỉ một chút tin tốt trong tất cả điều này là vấn đề không xảy ra nếu bạn có thể tránh ràng buộc với thuộc tính lồng nhau. Ví dụ, nếu bạn thêm một ToString() ghi đè lên Test2 để trả về giá trị của thuộc tính B của nó và loại bỏ DisplayMemberPath khỏi bạn listbox mục, vấn đề sẽ biến mất. ví dụ .:

public class Test2 
{ 
    public string B { get; set; } 

    public override string ToString() 
    { 
     return this.B; 
    } 
} 

<ListBox ItemsSource="{Binding Bs}" 
    Margin="5"/> 
+1

Ah có vấn đề rò rỉ ràng buộc khét tiếng. Điều này cũng đúng, tôi đã thấy nó. Mặc dù bạn có thể khắc phục nó bằng cách triển khai INotifyPropertyChanged trên mô hình của bạn. Ngay cả khi bạn không bao giờ gọi sự kiện PropertyChanged, sự hiện diện của nó chỉ khắc phục vấn đề trong khung công tác. Cũng trong liên kết, bạn có thể thêm Chế độ = OneWay hoặc Chế độ = OneTime để khắc phục sự cố rò rỉ ràng buộc. Mặc dù điều này không phải lúc nào cũng có sẵn. Hãy thử cả hai, thêm INotifyPropertyChanged và {Binding B, Mode = OneWay}. –

+0

@Nicole & Justin, Thx rất nhiều vì sự giúp đỡ của bạn. Sử dụng ToString() hoặc OneTime Binding cố định các cá thể Test2 mồ côi. Thx một lần nữa cho điều đó. Nhưng một cá thể duy nhất của mảng Test2 [] vẫn được ghim và chiếm 16 byte bộ nhớ. Mặc dù điều này là tốt hơn nhiều so với số lượng "n" của Test2 trường hợp gắn vào bộ nhớ, nhưng vẫn còn làm thế nào để loại bỏ điều đó? Tôi đã cố gắng 'INotifyPropertyChanged' cho bộ sưu tập' Bs' quá (cũng mệt mỏi OneTime ràng buộc của 'Bs') ... mảng vẫn còn. Bất kỳ trợ giúp ở đây? –

+0

@ justin.m.chase: Cảm ơn bạn đã đề cập đến giải pháp INotifyPropertyChanged.Nó không làm việc cho các vấn đề WinForms trước đó của tôi, vì vậy tôi hoàn toàn quên rằng tôi đã chạy qua đề cập đến nó cho WPF. –

0

Trong mã mẫu ở trên, tôi không thấy nơi bạn đang tải bất kỳ hình ảnh nào?

Nhưng giả sử rằng bạn đang dỡ toàn bộ chế độ xem, điều này vẫn có thể dự đoán được. Yếu tố bạn không tính đến là Dispatcher. Dispatcher là một hàng đợi sự kiện được ưu tiên và cho mỗi đại biểu trong hàng đợi đó, nó duy trì một tham chiếu đến các đối tượng được chỉ bởi các đại biểu đó. Có nghĩa là nó rất có thể cho một cái gì đó trong quan điểm của bạn để được trong hàng đợi sau khi một sự kiện Unloaded và do đó có một tài liệu tham khảo hợp pháp trong GC. Bạn có thể GC.Collect cho đến khi bạn có màu xanh trên mặt và nó sẽ không bao giờ thu thập các đối tượng với các tham chiếu còn lại.

Vì vậy, những gì bạn phải làm là để bơm điều phối và sau đó gọi GC.Collect. Một cái gì đó như thế này:

void Control_Unloaded(object sender, RoutedEventArgs e) 
{ 
    // flush dispatcher 
    this.Dispatcher.BeginInvoke(new Action(DoMemoryAnalysis), DispatcherPriority.ContextIdle); 
} 

private static void DoMemoryAnalysis() 
{ 
    GC.Collect(); 
    GC.WaitForPendingFinalizers(); 

    // do memory analysis now. 
} 

Một nguyên nhân phổ biến khác gây ra rò rỉ bộ nhớ trong .net phải thực hiện với việc đính kèm sự kiện và không tách chúng một cách chính xác. Tôi không thấy bạn làm điều này trong mẫu của bạn ở trên nhưng nếu bạn đang gắn các sự kiện ở tất cả chắc chắn rằng bạn đang unattaching chúng trong Unloaded hoặc Dispose hoặc bất cứ nơi nào là thích hợp nhất.

+0

Mô hình MVVM + DataTemplate không giữ bất kỳ khung nhìn nào, xem mô hình hoặc các đối tượng mô hình trong coe meme view được ghi đè khi một mô hình khung nhìn đích khác nhau được đặt. Ảnh chụp của tôi cho thấy Chế độ xem 'Test1' không hoạt động! Vì vậy, câu trả lời truy vấn đầu tiên của bạn ... Plus Tôi không có bất kỳ xử lý sự kiện rò rỉ như là gốc cho ** ** Pinned trường hợp là untraceable (cho bất kỳ xử lý sự kiện như vậy) bởi profiler. Như đã thấy trong mã của tôi, tôi thực sự không có bất kỳ sự kiện nào được xử lý (trừ khi WPF giữ tham chiếu còn sống vì một số lý do). Thêm ** Các trường hợp ghim ** luôn luôn có chủ ý và ví dụ của tôi không có bất kỳ mã nào như vậy. –

+0

WPF chính nó sẽ đưa hình ảnh của bạn vào hàng đợi điều phối cho tất cả các loại sự kiện. Bạn không cần phải treo lên các sự kiện trong mã của bạn để điều này xảy ra. Tôi đã có vấn đề này chính xác cùng một khi làm việc trên các nhà nhập khẩu PSD trong Expression Blend. Chúng tôi sẽ mở một tệp psd lớn, đóng hộp thoại và mở lại và gặp sự cố với bộ nhớ ngoài. Làm thế nào bạn có thể được ra khỏi bộ nhớ nếu cửa sổ thậm chí không mở? Của nó bởi vì cửa sổ sống trong hàng đợi điều phối trong một thời gian khá dài. Bạn phải xóa hàng đợi và GC thu thập ngay cả khi hình ảnh không còn trong cây hình ảnh nữa. Thử nó. –

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