6

Tôi đang làm việc với số lượng lớn các đối tượng (POI) đang được hiển thị trên MapControl. Tôi đang giúp bản thân mình với một MVVM Light để tuân theo các quy tắc của phương pháp MVVM.Windows Phone 8.1 Rò rỉ bộ nhớ WinRT với ObservableCollection

Vì tôi có nghĩa vụ hiển thị mọi đối tượng trên bản đồ, tôi phải sử dụng bộ sưu tập MapItemsControl chứ không phải MapElements. Bộ sưu tập này liên kết với đối tượng ObservableCollection<PushpinViewModel> (Pushpins) trong tương ứng ViewModel. Mọi thứ hoạt động như mong đợi, đến mức tối đa, khi tôi muốn làm mới Pushpins. Vấn đề là rò rỉ bộ nhớ. Nhưng trước tiên, một số mã để hình dung vấn đề:

XAML:

<maps:MapControl x:Name="Map" 
       x:Uid="MapControl"> 
    <maps:MapItemsControl ItemsSource="{Binding Pushpins}"> 
    <maps:MapItemsControl.ItemTemplate> 
     <DataTemplate> 
     <Image Source="{Binding Image}"/> 
     </DataTemplate> 
    </maps:MapItemsControl.ItemTemplate> 
    </maps:MapItemsControl> 

MainViewModel:

public class MainViewModel : ViewModelBase 
{ 
    public RelayCommand AddCommand { get; set; } 
    public RelayCommand ClearCommand { get; set; } 
    public RelayCommand CollectCommand { get; set; } 

    public ObservableCollection<PushpinViewModel> Pushpins { get; set; } 

    /* Ctor, initialization of Pushpins and stuff like that */ 

    private void Collect() 
    { 
     GC.Collect(2); 
     GC.WaitForPendingFinalizers(); 
     GC.Collect(2); 
     PrintCurrentMemory(); 
    } 

    private void Clear() 
    { 
     Pushpins.Clear(); 
     PrintCurrentMemory(); 
    } 

    private void Add() 
    { 
     for (int i = 0; i < 1000; i++) 
     { 
      Pushpins.Add(new PushpinViewModel()); 
     } 
     PrintCurrentMemory(); 
    } 

    private void PrintCurrentMemory() 
    { 
     Logger.Log(String.Format("Total Memory: {0}", GC.GetTotalMemory(true)/1024.0)); 
    } 
} 

PushpinViewModel:

public class PushpinViewModel: ViewModelBase 
{ 
    public string Image { get { return "/Assets/SomeImage.png"; } } 

    ~PushpinViewModel() 
    { 
     Logger.Log("This finalizer never gets called!"); 
    } 
} 

Bây giờ, hãy xem xét các tình huống sau. Tôi thêm vào bộ sưu tập yếu tố. Chúng được trả lại, bộ nhớ được phân bổ, mọi thứ đều ổn. Bây giờ tôi muốn xóa bộ sưu tập và thêm một bộ sưu tập khác (khác trong kịch bản thực) 1000 phần tử. Vì vậy, tôi gọi phương thức Clear(). Nhưng không có gì xảy ra! Pushpins bị xóa, nhưng PushpinViewModel của finalizers không được gọi! Sau đó, tôi thêm 1000 phần tử một lần nữa và việc sử dụng bộ nhớ của tôi tăng gấp đôi. Bạn có thể đoán điều gì sẽ xảy ra tiếp theo. Khi tôi lặp lại quy trình này Clear() - Add() 3-5 lần ứng dụng của tôi gặp sự cố.

Vì vậy, vấn đề là gì? Rõ ràng ObservableCollection đang giữ tham chiếu đến các đối tượng PushpinViewModel sau Clear() đã được thực hiện trên đó, do đó chúng không thể được thu thập rác. Tất nhiên buộc GC thực hiện thu gom rác thải không giúp (đôi khi thậm chí làm cho tình hình tồi tệ hơn).

Điều này khiến tôi bận tâm trong 2 ngày, tôi đã thử nhiều kịch bản khác nhau để thử và khắc phục vấn đề này, nhưng thành thật mà nói, không có gì hữu ích. Chỉ có một thứ không đáng giá - tôi không nhớ chính xác kịch bản, nhưng khi tôi đã giao Pushpins = null, và sau đó đã làm điều gì đó hơn, VehiceViewModel đã bị hủy. Nhưng điều đó không hiệu quả đối với tôi, bởi vì tôi cũng nhớ rằng tôi đã có vấn đề với việc hình dung các chân trên bản đồ sau Clear().

Bạn có bất kỳ ý tưởng nào có thể gây ra rò rỉ bộ nhớ này không? Làm thế nào tôi có thể buộc các thành viên của OC phá hủy? Có thể có một số loại thay thế cho OC? Cảm ơn bạn trước vì đã giúp đỡ!

EDIT:

tôi đã làm một số xét nghiệm với XAML bản đồ kiểm soát - https://xamlmapcontrol.codeplex.com/, và kết quả là đáng ngạc nhiên. Hiệu suất bản đồ tổng thể với> 1000 phần tử được thêm vào, kém hơn một số nguyên tố MapControl, NHƯNG, nếu tôi gọi Add() x1000, sau đó Clear(), sau đó Add() x1000, trình hoàn tất của PushpinViewModel sẽ được gọi! Bộ nhớ được giải phóng và ứng dụng không bị lỗi. Vì vậy, chắc chắn có điều gì đó sai trái với số MapControl của Microsoft ...

+0

Vấn đề là khả năng các bitmap bạn đang tải về mẫu đang được lưu trữ trong bộ nhớ. Thu thập GC không xóa nó khỏi bộ nhớ cache. Xem [những] này (http://stackoverflow.com/questions/1684489/how-do-you-make-sure-wpf-releases-large-bitmapsource-from-memory) [liên quan] (http://stackoverflow.com/questions/5530645/release-bitmapimages-used-as-image-control-source-memory-problem) Các câu hỏi. –

+0

Cảm ơn câu trả lời của bạn. Tôi đã điều tra khả năng này, và đồng ý rằng nó thực sự có thể là một lý do. Tôi đã kiểm tra các liên kết của bạn, câu trả lời đáng buồn được đăng có chủ yếu áp dụng cho các ứng dụng WPF (tôi đang trong một WinRT - Universal App). Tôi đã quản lý để tải một hình ảnh từ các dòng (như một phương pháp async, nhưng nó đã làm việc) - unfortunatelly nó đã không giúp đỡ. Tiêu thụ bộ nhớ thậm chí còn lớn hơn: ( – Malutek

+1

Tôi không thể nói với vấn đề cụ thể của bạn, nhưng tôi đã có một số vấn đề với MapItemsControl. Tôi đã có khá nhiều thiết lập tương tự bạn đã làm - liên kết với ObservableCollection bằng MVVM. Tôi điều hướng ra khỏi bản đồ để xem khác, sau đó trở lại bản đồ, và lặp đi lặp lại điều này qua lại 3-5 lần, tôi sẽ (thường) nhận được một lỗi "Truy cập Vi phạm" mà tôi thu hẹp xuống MapItemsControl. với một hành vi mà có một nguồn mặt hàng và vẽ pushpins trên bản đồ cho tôi thay vào đó, hãy cho tôi biết nếu bạn muốn tôi đăng nguồn. –

Trả lời

8

OK, đây là hành vi mà tôi thực hiện mô phỏng những gì MapItemsControl thực hiện.Lưu ý rằng điều này là khá untested - nó hoạt động trong ứng dụng của tôi, nhưng đã thực sự không được thử bất cứ nơi nào khác. Và tôi chưa bao giờ thử nghiệm chức năng RemoveItems vì ứng dụng của tôi chỉ thêm các mục vào một số ObservableCollection và xóa chúng; nó không bao giờ loại bỏ các mục tăng dần.

Cũng lưu ý rằng thẻ gắn thẻ đẩy XAML với mã băm của mục mà nó được ràng buộc; đây là cách nó xác định pushpins nào cần loại bỏ khỏi bản đồ nếu bộ sưu tập thay đổi. Điều này có thể không phù hợp với hoàn cảnh của bạn, nhưng nó có vẻ hiệu quả.

Cách sử dụng:

Lưu ý: NumberedCircle là một điều khiển người dùng mà chỉ đơn giản là một vòng tròn màu đỏ có hiển thị một số bên trong nó; thay thế bằng bất kỳ điều khiển XAML nào bạn muốn sử dụng làm đinh ghim. DestinationsObservableCollection của các đối tượng có thuộc tính Number (để hiển thị bên trong đinh ghim) và thuộc tính Point (vị trí đinh ghim).

<map:MapControl> 
    <i:Interaction.Behaviors> 
     <behaviors:PushpinCollectionBehavior ItemsSource="{Binding Path=Destinations}"> 
     <behaviors:PushpinCollectionBehavior.ItemTemplate> 
      <DataTemplate> 
       <controls:NumberedCircle Number="{Binding Path=Number}" map:MapControl.Location="{Binding Path=Point}" /> 
      </DataTemplate> 
     </behaviors:PushpinCollectionBehavior.ItemTemplate> 
     </behaviors:PushpinCollectionBehavior> 
    </i:Interaction.Behaviors> 
</map:MapControl> 

Code:

using System; 
using System.Collections; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.Collections.Specialized; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

using Microsoft.Xaml.Interactivity; 

using Windows.Devices.Geolocation; 
using Windows.Foundation; 
using Windows.Storage.Streams; 
using Windows.UI.Xaml; 
using Windows.UI.Xaml.Controls.Maps; 

namespace Foo.Behaviors 
{ 
    /// <summary> 
    /// Behavior to draw pushpins on a map. This effectively replaces MapItemsControl, which is flaky as hell. 
    /// </summary> 
    public class PushpinCollectionBehavior : DependencyObject, IBehavior 
    { 
     #region IBehavior 

     public DependencyObject AssociatedObject { get; private set; } 

     public void Attach(Windows.UI.Xaml.DependencyObject associatedObject) 
     { 
      var mapControl = associatedObject as MapControl; 

      if (mapControl == null) 
       throw new ArgumentException("PushpinCollectionBehavior can be attached only to MapControl"); 

      AssociatedObject = associatedObject; 

      mapControl.Unloaded += MapControlUnloaded; 
     } 

     public void Detach() 
     { 
      var mapControl = AssociatedObject as MapControl; 

      if (mapControl != null) 
       mapControl.Unloaded -= MapControlUnloaded; 
     } 

     #endregion 

     #region Dependency Properties 

     /// <summary> 
     /// The dependency property of the item that contains the pushpin locations. 
     /// </summary> 
     public static readonly DependencyProperty ItemsSourceProperty = 
      DependencyProperty.Register("ItemsSource", typeof(object), typeof(PushpinCollectionBehavior), new PropertyMetadata(null, OnItemsSourcePropertyChanged)); 

     /// <summary> 
     /// The item that contains the pushpin locations. 
     /// </summary> 
     public object ItemsSource 
     { 
      get { return GetValue(ItemsSourceProperty); } 
      set { SetValue(ItemsSourceProperty, value); } 
     } 

     /// <summary> 
     /// Adds, moves, or removes the pushpin when the item source changes. 
     /// </summary> 
     private static void OnItemsSourcePropertyChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) 
     { 
      var behavior = dependencyObject as PushpinCollectionBehavior; 
      var mapControl = behavior.AssociatedObject as MapControl; 

      // add the items 

      if (behavior.ItemsSource is IList) 
       behavior.AddItems(behavior.ItemsSource as IList); 
      else 
       throw new Exception("PushpinCollectionBehavior needs an IList as the items source."); 

      // subscribe to changes in the collection 

      if (behavior.ItemsSource is INotifyCollectionChanged) 
      { 
       var items = behavior.ItemsSource as INotifyCollectionChanged; 
       items.CollectionChanged += behavior.CollectionChanged; 
      } 
     } 

     // <summary> 
     /// The dependency property of the pushpin template. 
     /// </summary> 
     public static readonly DependencyProperty ItemTemplateProperty = 
      DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(PushpinCollectionBehavior), new PropertyMetadata(null)); 

     /// <summary> 
     /// The pushpin template. 
     /// </summary> 
     public DataTemplate ItemTemplate 
     { 
      get { return (DataTemplate)GetValue(ItemTemplateProperty); } 
      set { SetValue(ItemTemplateProperty, value); } 
     } 

     #endregion 

     #region Events 

     /// <summary> 
     /// Adds or removes the items on the map. 
     /// </summary> 
     private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
     { 
      switch (e.Action) 
      { 
       case NotifyCollectionChangedAction.Add: 
        AddItems(e.NewItems); 
        break; 

       case NotifyCollectionChangedAction.Remove: 
        RemoveItems(e.OldItems); 
        break; 

       case NotifyCollectionChangedAction.Reset: 
        ClearItems(); 
        break; 
      } 
     } 

     /// <summary> 
     /// Removes the CollectionChanged event handler from the ItemsSource when the map is unloaded. 
     /// </summary> 
     void MapControlUnloaded(object sender, RoutedEventArgs e) 
     { 
      var items = ItemsSource as INotifyCollectionChanged; 

      if (items != null) 
       items.CollectionChanged -= CollectionChanged; 
     } 

     #endregion 

     #region Private Functions 

     /// <summary> 
     /// Adds items to the map. 
     /// </summary> 
     private void AddItems(IList items) 
     { 
      var mapControl = AssociatedObject as MapControl; 

      foreach (var item in items) 
      { 
       var templateInstance = ItemTemplate.LoadContent() as FrameworkElement; 

       var hashCode = item.GetHashCode(); 

       templateInstance.Tag = hashCode; 
       templateInstance.DataContext = item; 

       mapControl.Children.Add(templateInstance); 

       Tags.Add(hashCode); 
      } 
     } 

     /// <summary> 
     /// Removes items from the map. 
     /// </summary> 
     private void RemoveItems(IList items) 
     { 
      var mapControl = AssociatedObject as MapControl; 

      foreach (var item in items) 
      { 
       var hashCode = item.GetHashCode(); 

       foreach (var child in mapControl.Children.Where(c => c is FrameworkElement)) 
       { 
        var frameworkElement = child as FrameworkElement; 

        if (hashCode.Equals(frameworkElement.Tag)) 
        { 
         mapControl.Children.Remove(frameworkElement); 
         continue; 
        } 
       } 

       Tags.Remove(hashCode); 
      } 
     } 

     /// <summary> 
     /// Clears items from the map. 
     /// </summary> 
     private void ClearItems() 
     { 
      var mapControl = AssociatedObject as MapControl; 

      foreach (var tag in Tags) 
      { 
       foreach (var child in mapControl.Children.Where(c => c is FrameworkElement)) 
       { 
        var frameworkElement = child as FrameworkElement; 

        if (tag.Equals(frameworkElement.Tag)) 
        { 
         mapControl.Children.Remove(frameworkElement); 
         continue; 
        } 
       } 
      } 

      Tags.Clear(); 
     } 

     #endregion 

     #region Private Properties 

     /// <summary> 
     /// The object tags of the items this behavior has placed on the map. 
     /// </summary> 
     private List<int> Tags 
     { 
      get 
      { 
       if (_tags == null) 
        _tags = new List<int>(); 

       return _tags; 
      } 
     } 
     private List<int> _tags; 

     #endregion 
    } 
} 
+0

Cảm ơn giải pháp này. – Mitius

+2

Cảm ơn, tôi nhận được lỗi "Vi phạm Truy cập" sau 2-3 lần lặp lại, giải pháp này đã giúp – Alexandr

+0

Cảm ơn rất nhiều, đã khiến tôi gặp rắc rối. Mặc dù tôi có một vấn đề với một lệnh tôi đã có bên trong MapItemControl DataTemplate mà ngừng hoạt động sau khi tôi chuyển sang mã của bạn. Bất kỳ ý tưởng tại sao? – stambikk