6

Sto lavorando con una grande quantità di oggetti (POI) che vengono visualizzati su un MapControl. Mi sto aiutando con un MVVM Light ad obbedire alle regole dell'approccio MVVM.Perdita di memoria Windows Phone 8.1 WinRT con ObservableCollection

Poiché sono obbligato a visualizzare tutti gli oggetti sulla mappa, devo utilizzare la raccolta MapItemsControl e non quella di MapElements. Questa raccolta si lega all'oggetto ObservableCollection<PushpinViewModel> (Pushpins) nel corrispondente ViewModel. Tutto funziona come previsto, fino al punto, quando voglio aggiornare Pushpins. Il problema è la perdita di memoria. Ma prima, un po 'di codice per visualizzare il problema:

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

Ora, si consideri il seguente scenario. Aggiungo alla collezione Pushpins elementi 1000 PushpinViewModel. Sono resi, la memoria è allocata, va tutto bene. Ora voglio cancellare la raccolta e aggiungere un altro (diverso nello scenario reale) 1000 elementi. Quindi, chiamo il metodo Clear(). Ma .. non succede nulla! Pushpins viene cancellato, ma i finalizzatori di PushpinViewModel non vengono richiamati! Quindi aggiungo di nuovo 1000 elementi e l'utilizzo della memoria raddoppia. Puoi indovinare cosa succederà dopo. Quando ripeto questa procedura Clear() - Add() per 3-5 volte la mia app si arresta in modo anomalo.

Quindi, qual è il problema? Chiaramente, ObservableCollection contiene i riferimenti agli oggetti PushpinViewModel dopo che è stato eseguito su Clear(), quindi non possono essere raccolti. Ovviamente forzare GC a eseguire la garbage collection non aiuta (a volte peggiora persino la situazione).

Mi sta dando fastidio per 2 giorni, ho provato molti scenari diversi per cercare di superare questo problema, ma ad essere sincero, nulla ha aiutato. C'era solo una cosa che non valeva niente - non ricordo lo scenario esatto, ma quando ho assegnato lo Pushpins = null e poi ho fatto qualcosa di più, gli VehiceViewModel sono stati distrutti. Ma questo non funziona per me, perché ricordo anche che ho avuto problemi con la visualizzazione di questi pin sulla mappa dopo il Clear().

Avete qualche idea su cosa può causare questa perdita di memoria? Come posso costringere i membri di OC a distruggere? Forse c'è un qualche tipo di alternativa per OC? Grazie in anticipo per qualsiasi aiuto!

EDIT:

ho fatto alcuni test con XAML Map Control - https://xamlmapcontrol.codeplex.com/, ed i risultati sono sorprendenti. Le prestazioni complessive della mappa con oltre 1000 elementi aggiunti sono inferiori a quelle native MapControl, MA, se chiamo Add() x1000, quindi Clear(), quindi Add() x1000, i finalizzatori di PushpinViewModel vengono chiamati! La memoria viene liberata e l'app non si arresta in modo anomalo. Quindi c'è sicuramente qualcosa di sbagliato con Microsoft MapControl ...

+0

Il problema è probabile che le bitmap si sta caricando al modulo vengono memorizzate nella cache in memoria. La raccolta del GC non la rimuove dalla cache. Vedi [questi] (http://stackoverflow.com/questions/1684489/how-do-you-make-sure-wpf-releases-large-bitmapsource-from-memory) [related] (http://stackoverflow.com)/questions/5530645/releasing-bitmapimages-used-as-image-control-source-memory-problem) domande. –

+0

Grazie per la risposta. Stavo indagando su questa possibilità e sono d'accordo sul fatto che potrebbe davvero essere una ragione. Ho controllato i tuoi link, purtroppo le risposte postate sono per lo più applicate alle applicazioni WPF (sono in una WinRT - App universale). Sono riuscito a caricare un'immagine dal flusso (come un metodo asincrono, ma ha funzionato) - sfortunatamente non ha aiutato. Il consumo di memoria è ancora maggiore: ( – Malutek

+1

Non riesco a parlare con il tuo problema specifico, ma ho riscontrato alcuni problemi con MapItemsControl. Avevo praticamente la stessa configurazione che hai fatto: esegui il bind su ObservableCollection usando MVVM. Ho navigato lontano dalla mappa in un'altra vista, poi di nuovo alla mappa e ripetuto questo avanti e indietro per 3-5 volte, avrei (di solito) ottenuto un errore di "Violazione di accesso" che ho ristretto a MapItemsControl. con un comportamento che prende una fonte di elementi e disegna invece per me simboli sulla mappa, fammi sapere se vuoi che io pubblichi la fonte –

risposta

8

OK, ecco il comportamento che ho fatto che emula ciò che fa MapItemsControl.Si noti che questo è abbastanza non testato: funziona nella mia app, ma non è mai stato provato altrove. E non ho mai testato la funzione RemoveItems perché la mia app aggiunge solo elementi a ObservableCollection e li cancella; non rimuove mai gli oggetti in modo incrementale.

Si noti inoltre che contrassegna i simboli XAML con il codice hash dell'elemento a cui è associato; questo è il modo in cui identifica i simboli da rimuovere dalla mappa se la collezione cambia. Questo potrebbe non funzionare per le tue circostanze, ma sembra essere efficace.

utilizzati:

Nota: NumberedCircle è un controllo utente che è semplicemente un cerchio rosso che visualizza un numero all'interno di esso; sostituire con qualsiasi controllo XAML che si desidera utilizzare come una puntina da disegno. Destinations è il mio ObservableCollection di oggetti che hanno una proprietà Number (da visualizzare all'interno della puntina da disegno) e una proprietà Point (la posizione del puntino).

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

Codice:

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

Grazie per questa soluzione. – Mitius

+2

Grazie, ricevo un errore "Violazione di accesso" dopo 2-3 ripetizioni, questa soluzione ha aiutato – Alexandr

+0

Grazie mille, mi ha causato un sacco di problemi. Anche se ho un problema con un comando che avevo all'interno del DataTemplate di MapItemsControl che ha smesso di funzionare dopo essere passato al tuo codice. Qualche idea del perché? – stambikk

Problemi correlati