Search code examples
c#uwpmemory-leaksuwp-maps

UWP MapControl unmanaged memory leak


I have a MapControl where I add MapIcons and Polylines. After a few minutes of work app starts leaking unmanaged memory and goes until 8Gb.

Please, help me to find a bug. Thank you!

I have used dotMemory profiler. The screenshot below showing leaking memory.

enter image description here

C#

Every second I redraw a MapControl (mainMap).

 _timerUpdateDevices = ThreadPoolTimer.CreatePeriodicTimer(async (t) =>
                {
                   // ... some managed code to prepare geodata ...

                    await mainMap.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () =>
                    {
                        if (Interlocked.Read(ref canInsertNewCoordsForSelectedDeviceTracker) == 1)
                        {
                            RenderTracks(deviceID, tracker);
                        }
                        else
                        {
                            var trackLayer = cmap.CommonLayers.First(x => x.CMLayerType == CMLayerTypeEnum.Track);
                            var trackMapLayer = mainMap.Layers.FirstOrDefault(x => x.ZIndex == trackLayer.ZIndex);
                            if (trackMapLayer != null)
                            {
                                if (((MapElementsLayer)trackMapLayer).MapElements.Count > 0) ((MapElementsLayer)trackMapLayer).MapElements.Clear();
                            }
                        }
                        await RenderMap();
                    });
                }, TimeSpan.FromSeconds(1));


   private void RenderTracks(int deviceID, LGPSTracker tracker)
        {
            try
            {
                if (userSelectedDevice != null)
                {
                    #region Track layer 
                    
                    var trackMapLayer = mainMap.Layers.FirstOrDefault(x => x.ZIndex == cmap.CommonLayers.First(x => x.CMLayerType == CMLayerTypeEnum.Track).ZIndex);

                    if (selectedDeviceTrackerPositions.Any() && selectedDeviceTrackerPositions.Count >= 2)
                    {                        
                        if (((MapElementsLayer)trackMapLayer).MapElements.Count > 0) // Update
                        {
                            ((MapElementsLayer)trackMapLayer).MapElements.Clear(); // Clean

                            LTrackerPosition[][] chunks = null; 

                            lock (selectedDeviceTrackerPositionsLock)
                            {
                                chunks = selectedDeviceTrackerPositions.Select((s, i) => new { Value = s, Index = i }).GroupBy(x => x.Index / 2).Select(grp => grp.Select(x => x.Value).ToArray()).ToArray();
                            }

                            LTrackerPosition lastInChunck = null;
                            for (int i = 0; i < chunks.Length; i++)
                            {
                                if (chunks[i].Length != 2) continue;

                                var firstPoint = chunks[i][0];
                                var secondPoint = chunks[i][1];

                                var mapPolyline = new MapPolyline();
                                var geoPositions = new List<BasicGeoposition>();
                                if (lastInChunck != null)
                                {
                                    geoPositions.Add(new BasicGeoposition() { Latitude = lastInChunck.Lat, Longitude = lastInChunck.Lng });
                                }
                                geoPositions.Add(new BasicGeoposition() { Latitude = firstPoint.Lat, Longitude = firstPoint.Lng });
                                geoPositions.Add(new BasicGeoposition() { Latitude = secondPoint.Lat, Longitude = secondPoint.Lng });
                                mapPolyline.Path = new Geopath(geoPositions);
                                mapPolyline.StrokeColor = Colors.Blue;
                                mapPolyline.StrokeThickness = trackMapPolylineStrokeThickness;
                                mapPolyline.Tag = new TrackerView() { Position = firstPoint, Device = userSelectedDevice, Tracker = tracker };
                                ((MapElementsLayer)trackMapLayer).MapElements.Add(mapPolyline);

                                lastInChunck = chunks[i][1];
                            }                            
                        }
                        else // Add
                        {
                            LTrackerPosition[][] chunks = null;

                            lock (selectedDeviceTrackerPositionsLock)
                            {
                                chunks = selectedDeviceTrackerPositions.Select((s, i) => new { Value = s, Index = i }).GroupBy(x => x.Index / 2).Select(grp => grp.Select(x => x.Value).ToArray()).ToArray();
                            }

                            LTrackerPosition lastInChunck = null;
                            for (int i = 0; i < chunks.Length; i++)
                            {
                                if (chunks[i].Length != 2) continue;

                                var firstPoint = chunks[i][0];
                                var secondPoint = chunks[i][1];

                                var mapPolyline = new MapPolyline();
                                var geoPositions = new List<BasicGeoposition>();
                                if (lastInChunck != null)
                                {
                                    geoPositions.Add(new BasicGeoposition() { Latitude = lastInChunck.Lat, Longitude = lastInChunck.Lng });
                                }
                                geoPositions.Add(new BasicGeoposition() { Latitude = firstPoint.Lat, Longitude = firstPoint.Lng });
                                geoPositions.Add(new BasicGeoposition() { Latitude = secondPoint.Lat, Longitude = secondPoint.Lng });
                                mapPolyline.Path = new Geopath(geoPositions);
                                mapPolyline.StrokeColor = Colors.Blue;
                                mapPolyline.StrokeThickness = trackMapPolylineStrokeThickness;
                                mapPolyline.Tag = new TrackerView() { Position = firstPoint, Device = userSelectedDevice, Tracker = tracker };
                                ((MapElementsLayer)trackMapLayer).MapElements.Add(mapPolyline);

                                lastInChunck = chunks[i][1];
                            }
                        }
                    }

                    #endregion                    
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }


  private async Task RenderMap()
        {
            var devices = ((App)Application.Current).Devices;
            var trackers = ((App)Application.Current).Trackers;
            var deviceTrackers = ((App)Application.Current).DeviceGPSTrackers;

            foreach (var clayer in cmap.CommonLayers)
            {
                if (clayer.CMLayerType == CMLayerTypeEnum.Transport)
                {
                    #region Tranport layer

                    var layer = mainMap.Layers.FirstOrDefault(x => x.ZIndex == clayer.ZIndex);
                    if (layer != null)
                    {
                        if (((MapElementsLayer)layer).MapElements.Count == 0)
      {
                            List<TrackerView> trackerPoints = new List<TrackerView>();
                            var isCoordsInterpolating = DefaultSettings.GetIsCoordsInterpolating();
                            if (isCoordsInterpolating)
                            {
                                if (clayer.TrackerPoints.Count == 0) return;
                                trackerPoints = clayer.TrackerPoints.Select(d => d.Value).ToList<TrackerView>();
                            }
                            else
                            {
                                if (clayer.TrackerPoints.Count == 0) return;
                                trackerPoints = clayer.TrackerPoints.Select(d => d.Value).ToList<TrackerView>();
                            }

                            foreach (var trackerPoint in trackerPoints)
                            {
                                var bg = new BasicGeoposition { Latitude = trackerPoint.Position.Lat, Longitude = trackerPoint.Position.Lng };

                                var mi = new MapIcon
                                {
                                    Location = new Geopoint(bg),
                                    NormalizedAnchorPoint = new Point(0.5, 1.0),
                                    ZIndex = 0,
                                    Tag = trackerPoint,
                                    IsEnabled = true,
                                    Visible = true,
                                    CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible,

                                };

                                mi.Image = await GetDeviceStateIconStreamNormal(trackerPoint.Device.DeviceTypeID, 2, trackerPoint.Device.Name);

                                ((MapElementsLayer)layer).MapElements.Add(mi); 
                            }
                        }
                        else 
                        {
                            #region Add cars

                            List<TrackerView> trackerPoints = new List<TrackerView>();
                            var isCoordsInterpolating = DefaultSettings.GetIsCoordsInterpolating();
                            if (isCoordsInterpolating)
                            {
                                if (clayer.TrackerPoints.Count == 0) return;
                                trackerPoints = clayer.TrackerPoints.Select(d => d.Value).ToList<TrackerView>();
                            }
                            else
                            {
                                if (clayer.TrackerPoints.Count == 0) return;
                                trackerPoints = clayer.TrackerPoints.Select(d => d.Value).ToList<TrackerView>();
                            }

                            var toAdd = new List<MapIcon>();

                            foreach (var trackerPoint in trackerPoints)
                            {
                                var isLayerElement = ((MapElementsLayer)layer).MapElements.FirstOrDefault(x => ((TrackerView)x.Tag).Device.ID == trackerPoint.Device.ID);
                                if (isLayerElement == null) // Добавить
                                {
                                    var bg = new BasicGeoposition { Latitude = trackerPoint.Position.Lat, Longitude = trackerPoint.Position.Lng };

                                    var mi = new MapIcon
                                    {
                                        Location = new Geopoint(bg),
                                        NormalizedAnchorPoint = new Point(0.5, 1.0),
                                        ZIndex = 0,
                                        Tag = trackerPoint,
                                        IsEnabled = true,
                                        Visible = true,
                                        CollisionBehaviorDesired = MapElementCollisionBehavior.RemainVisible,
                                    };

                                    if (userSelectedDevice != null) // Сделать прозрачным весьтранспорт кроме выделенного
                                    {
                                        if (userSelectedDevice.ID == trackerPoint.Device.ID)
                                        {
                                            mi.Image = await GetDeviceStateIconStreamSelected(trackerPoint.Device.DeviceTypeID, 2, trackerPoint.Device.Name);
                                        }
                                        else
                                        {
                                            mi.Image = await GetDeviceStateIconStreamFireless(trackerPoint.Device.DeviceTypeID, 2, trackerPoint.Device.Name, 0.4f);
                                        }
                                    }
                                    else // Update
                                    {
                                        mi.Image = await GetDeviceStateIconStreamNormal(trackerPoint.Device.DeviceTypeID, 2, trackerPoint.Device.Name);
                                    }

                                    ((MapElementsLayer)layer).MapElements.Add(mi);
                                    
                                }
                                else // Update
                                {
                                    var mi = (MapIcon)isLayerElement;
                                    var posMapElement = mi.Location.Position;
                                    if (posMapElement.Latitude != trackerPoint.Position.Lat || posMapElement.Longitude != trackerPoint.Position.Lng)
                                    {
                                        var bg = new BasicGeoposition { Latitude = trackerPoint.Position.Lat, Longitude = trackerPoint.Position.Lng };
                                        ((MapIcon)isLayerElement).Location = new Geopoint(bg);

                                        if (userSelectedDevice != null) 
                                        {
                                            if (userSelectedDevice.ID == trackerPoint.Device.ID)
                                            {
                                                mi.Image = await GetDeviceStateIconStreamSelected(trackerPoint.Device.DeviceTypeID, 2, trackerPoint.Device.Name);
                                            }
                                            else
                                            {
                                                mi.Image = await GetDeviceStateIconStreamFireless(trackerPoint.Device.DeviceTypeID, 2, trackerPoint.Device.Name, 0.5f);
                                            }
                                        }
                                        else 
                                        {
                                            mi.Image = await GetDeviceStateIconStreamNormal(trackerPoint.Device.DeviceTypeID, 2, trackerPoint.Device.Name);
                                        }                                        
                                    }
                                }
                            }

                            #endregion

                            #region Delete it from map

                            foreach (var device in devices.Where(x => x.IsEnabled == 0))
                            {
                                var isLayerElementExisting = ((MapElementsLayer)layer).MapElements.FirstOrDefault(x => ((TrackerView)x.Tag).Device.ID == device.ID);
                                if (isLayerElementExisting != null)
                                {
                                    ((MapElementsLayer)layer).MapElements.Remove(isLayerElementExisting);
                                }
                            }

                            #endregion
                        }

                        break;
                    }
                    #endregion
                }
            }
        } // private void RenderMap()

// Create  map icon on fly
private async Task<IRandomAccessStreamReference> GetDeviceStateIconStreamNormal(int deviceTypeID, int deviceStateID, string text)
        {
            try
            {
                var deviceTypeIcon = deviceTypeIcons.FirstOrDefault(x => x.DeviceTypeId == deviceTypeID && x.DeviceStateId == deviceStateID);
                if (deviceTypeIcon != null)
                {
                    var stream = new MemoryStream(deviceTypeIcon.IconFile);
                    var randomAccessStream = new InMemoryRandomAccessStream();
                    var outputStream = randomAccessStream.GetOutputStreamAt(0);
                    await RandomAccessStream.CopyAndCloseAsync(stream.AsInputStream(), outputStream);
                    var imagedecoder = await BitmapDecoder.CreateAsync(randomAccessStream);
                    var device = CanvasDevice.GetSharedDevice();
                    var renderTarget = new CanvasRenderTarget(device, imagedecoder.PixelWidth, imagedecoder.PixelHeight, 96);

                    using (var ds = renderTarget.CreateDrawingSession())
                    {
                        ds.Clear(Colors.Transparent);
                        Color color = Colors.Black;

                        var image = await CanvasBitmap.LoadAsync(device, randomAccessStream, 96);
                        var destinationRectangle = new Rect() { Width = imagedecoder.PixelWidth, Height = imagedecoder.PixelHeight, X = 1, Y = 1 };
                        var sourceRectangle = new Rect() { Width = imagedecoder.PixelWidth, Height = imagedecoder.PixelHeight, X = 1, Y = 1 };
                        ds.DrawImage(image, destinationRectangle, sourceRectangle);

                        ds.DrawText(text, new System.Numerics.Vector2(2, 1), color, new CanvasTextFormat()
                        {
                            FontSize = 12,
                            FontWeight = Windows.UI.Text.FontWeights.Bold
                        });
                    }

                    await renderTarget.SaveAsync(randomAccessStream, CanvasBitmapFileFormat.Png);
                    await randomAccessStream.FlushAsync();
                    return RandomAccessStreamReference.CreateFromStream(randomAccessStream);
                }
                else
                {
                    return null; // TODO use default image
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex);
            }
            return null;
        }

Solution

  • Thanks to @JonasH and @Ed Pavlov.

    This code does not have any bugs provocation for memory leaks.

    I found a couple of leaks with some lambda expresiion += (s,e) => for Tile's method of MapControl. So I've fixed it.

    Anyway just making Zoom in/out of UWP MapControl during in a minutes increase memory a lots.