Tuesday, 15 June 2010

c# - Windows Phone 8.1 WinRT memory leak with ObservableCollection -



c# - Windows Phone 8.1 WinRT memory leak with ObservableCollection -

i'm working big amount of objects (poi's) getting displayed on mapcontrol. i'm helping myself mvvm lite obey rules of mvvm approach.

since i'm obligated display every object on map, have utilize mapitemscontrol collection, , not mapelements one. collection binds the observablecollection<pushpinviewmodel> object (pushpins) in corresponding viewmodel. works expected, point, when want refresh pushpins. problem memory leak. first, code visualize problem:

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 , stuff */ private void collect() { gc.collect(2); gc.waitforpendingfinalizers(); gc.collect(2); printcurrentmemory(); } private void clear() { pushpins.clear(); printcurrentmemory(); } private void add() { (int = 0; < 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 { { homecoming "/assets/someimage.png"; } } ~pushpinviewmodel() { logger.log("this finalizer never gets called!"); } }

now, consider next scenario. add together pushpins collection 1000 pushpinviewmodel elements. rendered, memory allocated, everything's fine. want clear collection, , add together (different in real scenario) 1000 elements. so, phone call clear() method. but.. nil happens! pushpins gets cleared, pushpinviewmodel's finalizers not called! add together 1000 elements again, , memory usage doubles. can guess happens next. when repeat clear() - add() procedure 3-5 times app crashes.

so, problem? observablecollection holding references pushpinviewmodel objects after clear() has been performed on it, cannot garbage collected. of course of study forcing gc perform garbage collection not help (it makes situation worse).

it's bothering me 2 days now, have tried many different scenarios seek , overcome problem, honest, nil helped. there 1 thing worth nil - don't remember exact scenario, when had assigned pushpins = null, , did more, vehiceviewmodel's destroyed. not work me, because remember had problem visualizing these pins on map after clear().

do have ideas can cause memory leak? how can forcefulness oc's members destroy? maybe there kind of alternative oc? in advance help!

edit:

i did tests xaml map command - https://xamlmapcontrol.codeplex.com/, , results surprising. overall map performance >1000 elements added it, poorer native mapcontrol, but, if phone call add() x1000, clear(), add() x1000, pushpinviewmodel's finalizers geting called! memory gets freed, , app not crash. there wrong microsoft's mapcontrol...

ok, here's behavior made emulates mapitemscontrol does. note pretty untested -- works in app, has not been tried anywhere else. , have never tested removeitems function because app adds items observablecollection , clears them; never removes items incrementally.

also note tags xaml pushpins hash code of item bound to; how identifies pushpins remove map if collection changes. may not work circumstances, seems effective.

usage:

note: numberedcircle user command reddish circle displays number within of it; replace whatever xaml command want utilize pushpin. destinations observablecollection of objects have number property (to display within pushpin) , point property (the pushpin location).

<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 draw pushpins on map. replaces mapitemscontrol, flaky 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 mapcontrol; if (mapcontrol == null) throw new argumentexception("pushpincollectionbehavior can attached mapcontrol"); associatedobject = associatedobject; mapcontrol.unloaded += mapcontrolunloaded; } public void detach() { var mapcontrol = associatedobject mapcontrol; if (mapcontrol != null) mapcontrol.unloaded -= mapcontrolunloaded; } #endregion #region dependency properties /// <summary> /// dependency property of item contains pushpin locations. /// </summary> public static readonly dependencyproperty itemssourceproperty = dependencyproperty.register("itemssource", typeof(object), typeof(pushpincollectionbehavior), new propertymetadata(null, onitemssourcepropertychanged)); /// <summary> /// item contains pushpin locations. /// </summary> public object itemssource { { homecoming getvalue(itemssourceproperty); } set { setvalue(itemssourceproperty, value); } } /// <summary> /// adds, moves, or removes pushpin when item source changes. /// </summary> private static void onitemssourcepropertychanged(dependencyobject dependencyobject, dependencypropertychangedeventargs e) { var behavior = dependencyobject pushpincollectionbehavior; var mapcontrol = behavior.associatedobject mapcontrol; // add together items if (behavior.itemssource ilist) behavior.additems(behavior.itemssource ilist); else throw new exception("pushpincollectionbehavior needs ilist items source."); // subscribe changes in collection if (behavior.itemssource inotifycollectionchanged) { var items = behavior.itemssource inotifycollectionchanged; items.collectionchanged += behavior.collectionchanged; } } // <summary> /// dependency property of pushpin template. /// </summary> public static readonly dependencyproperty itemtemplateproperty = dependencyproperty.register("itemtemplate", typeof(datatemplate), typeof(pushpincollectionbehavior), new propertymetadata(null)); /// <summary> /// pushpin template. /// </summary> public datatemplate itemtemplate { { homecoming (datatemplate)getvalue(itemtemplateproperty); } set { setvalue(itemtemplateproperty, value); } } #endregion #region events /// <summary> /// adds or removes items on 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 collectionchanged event handler itemssource when map unloaded. /// </summary> void mapcontrolunloaded(object sender, routedeventargs e) { var items = itemssource inotifycollectionchanged; if (items != null) items.collectionchanged -= collectionchanged; } #endregion #region private functions /// <summary> /// adds items map. /// </summary> private void additems(ilist items) { var mapcontrol = associatedobject mapcontrol; foreach (var item in items) { var templateinstance = itemtemplate.loadcontent() frameworkelement; var hashcode = item.gethashcode(); templateinstance.tag = hashcode; templateinstance.datacontext = item; mapcontrol.children.add(templateinstance); tags.add(hashcode); } } /// <summary> /// removes items map. /// </summary> private void removeitems(ilist items) { var mapcontrol = associatedobject mapcontrol; foreach (var item in items) { var hashcode = item.gethashcode(); foreach (var kid in mapcontrol.children.where(c => c frameworkelement)) { var frameworkelement = kid frameworkelement; if (hashcode.equals(frameworkelement.tag)) { mapcontrol.children.remove(frameworkelement); continue; } } tags.remove(hashcode); } } /// <summary> /// clears items map. /// </summary> private void clearitems() { var mapcontrol = associatedobject mapcontrol; foreach (var tag in tags) { foreach (var kid in mapcontrol.children.where(c => c frameworkelement)) { var frameworkelement = kid frameworkelement; if (tag.equals(frameworkelement.tag)) { mapcontrol.children.remove(frameworkelement); continue; } } } tags.clear(); } #endregion #region private properties /// <summary> /// object tags of items behavior has placed on map. /// </summary> private list<int> tags { { if (_tags == null) _tags = new list<int>(); homecoming _tags; } } private list<int> _tags; #endregion } }

c# windows-phone-8 memory-leaks windows-phone-8.1 observablecollection

No comments:

Post a Comment