2

First off, I freely admit that I don't understand memory management very well, so I may be missing something obvious here. I've been looking through articles, etc. such as this, but haven't found anything helpful.

I'm looping through a collection of StorageFiles and creating a SlideTemplate for each one. (The SlideTemplate is just a UserControl containing an InkCanvas.) I'm then loading strokes into the InkCanvas from the file stream and adding the SlideTemplate to my ObservableCollection<SlideTemplate>. Here's the code:

foreach (var slide in slides)
{
    using (var stream = await slide.OpenSequentialReadAsync())
    {
        InkCanvas inkCanvas = new InkCanvas();
        SlideTemplate newSlide = new SlideTemplate();

        await inkCanvas.InkPresenter.StrokeContainer.LoadAsync(stream);
        newSlide.Set_Canvas(inkCanvas);
        newSlide.Set_PenSize(new Size(DataModel.Instance.CurrentProject.PenSize, DataModel.Instance.CurrentProject.PenSize));
        DataModel.Instance.CurrentProject.Slides.Add(newSlide);
    }
}

When it gets to about number 61 or 62 it throws an exception on the "new InkCanvas()", saying that it "could not create instance of... InkCanvas." Here's the memory usage from the VS Diagnostic tools when it throws the exception:

Memory Usage

The memory usage just keeps climbing as it loops through them. Putting this:

inkCanvas = null;
GC.Collect();
GC.WaitForPendingFinalizers();

in the foreach actually loads all eighty slides successfully, but once it's done the memory usage stays put and won't drop again. And then going back out (which uses a similar method to save the ink strokes back to the file) and reloading the project throws the same error using about 150MB of memory. Can someone please explain what's at play here, or link to a helpful article?

Community
  • 1
  • 1
  • Why can't you load slides on demand, instead of loading all 80 slides into memory? – Scott Hutchinson Jan 19 '16 at 00:46
  • Would be possible, but it would absolutely kill navigation. –  Jan 19 '16 at 00:56
  • How much space do these 80 slides take up on disk compared to the 150MB of RAM that's getting used? – Scott Hutchinson Jan 19 '16 at 01:22
  • @ScottHutchinson 12MB. I'd even be (almost) fine with it hitting the 84MB *if* I could release it again. As is, it just keeps piling up. –  Jan 19 '16 at 02:43
  • Your DataModel is still storing those slides. That might be the reason GC.collect() has no effect in reducing the memory consumption. – Vishnu Prasad V Jan 19 '16 at 04:38
  • I think I would comment out all the code inside the using block, and then run it to observe the memory usage. If it behaves well like that, then I would start uncommenting one line at a time and running/observing again, until I start seeing the memory issue. That way you should be able to pinpoint what line is causing the problem, so you could focus your efforts on fixing that line somehow. – Scott Hutchinson Jan 19 '16 at 05:04
  • The other thing I would do is double-check all the objects involved for a Dispose and/or Finalize method. If they're there (and especially if I'm having memory issues), then I would make sure they get called as soon s I'm done with the object. – Scott Hutchinson Jan 19 '16 at 15:15
  • @ScottHutchinson Ok did what you suggested and I think I found the problem. Storing the slides is what seems to take the memory. I hate to do it, but I guess I'll just have to load only the slides that are needed, or maybe about 5 of them at a time. –  Jan 19 '16 at 15:45
  • I wish I knew why they seem to take up about 12x as much memory as expected. I hope the slides will load quicker than you expect, so the delay will not be too bad. Of course, if you have control of the hardware, you could make sure they get stored or cached on a solid-state drive to make it faster if necessary. – Scott Hutchinson Jan 19 '16 at 16:24
  • I wonder if you could try something different than the ObservableCollection, like store them in a plain-old List or even an Array to see if that makes any difference. – Scott Hutchinson Jan 19 '16 at 16:30
  • @ScottHutchinson I agree. Unfortunately I don't have that control. I'll try a `List` when I go for lunch in about 45 minutes here. –  Jan 19 '16 at 16:31
  • @ScottHutchinson No, storing in a List doesn't seem to make any difference. –  Jan 20 '16 at 15:35
  • OK. Thanks for being my guinea pig on that one! ;o) – Scott Hutchinson Jan 20 '16 at 15:46
  • Here's a wacky idea that may or may not be feasible. Before you save the slides to disk you could compress them, and then they would still be compressed when you load them into the ObservableCollection. Then when you need to get one from the collection for the user to view/edit, you decompress it, and then compress it again when you put it back in the collection or save it to disk. There are two assumptions: (1) Compression will save a lot of memory; and (2) compression/decompression will be much faster than reading/writing to disk. I might try to do a proof-of-principle test on this. [cont'd] – Scott Hutchinson Jan 20 '16 at 17:04
  • Here's a free open source C# library that might do the compression.: http://icsharpcode.github.io/SharpZipLib/ – Scott Hutchinson Jan 20 '16 at 17:05
  • @ScottHutchinson I'll give it a try, thanks! –  Jan 20 '16 at 18:00
  • The next thing I would do is manually add the slides to a zip file (in Windows, right-click, Send To compressed zip folder) as a test. If the zip file is not significantly smaller than the uncompressed files, then there is probably no point in spending any more time on this idea. By the way, if you want to move this discussion to chat, that's fine with me; I tried it and it failed, but it didn't say why. I've never used chat yet, so it's your call. – Scott Hutchinson Jan 20 '16 at 18:44
  • Good point. I tried it and it's about .2 MB less, so probably not worth it :P. Hmm... It fails for me, too. Weird. –  Jan 20 '16 at 18:48
  • I just found the link below, which documents an instance of a memory leak in a UWP app that occurs only when running in the Visual Studio debugger. So you might want to run your app without debugging and independent of Visual Studio to confirm that the leak is still there (if you haven't already done so). You could probably use Task Manager to observe the memory usage of your app. https://social.msdn.microsoft.com/Forums/en-US/c82cdf4a-fbb0-4e61-91ff-6a479a35f535/action?threadDisplayName=windowsapps&forum=wpdevelop – Scott Hutchinson Jan 21 '16 at 03:06
  • Here's another shot in the dark. If you could set the Visual Studio Configuration Manager to compile your solution to run on an x64 platform instead of the default (Any CPU), that should give the app more memory, which might prevent the exception. – Scott Hutchinson Jan 21 '16 at 03:17
  • I've gotten reports of it crashing with a high number of slides in real-life scenarios (which is what actually notified me of the issue), so it doesn't seem that it's only when debugging. Also, I tried setting it to x64 but it makes no difference :( –  Jan 21 '16 at 03:29
  • Here's another path to try, but I'm still trying to figure out the details. Check the Parent property of the SlideTemplate. If possible, it should be the child of a form (page??) or some other FrameworkElement. If not, then you might be able to add it as a child to a form using the AddVisualChild method. That way when the form closes, it should dispose of all its children, including all the Slide Templates, which should then get garbage collected eventually (especially if you call GC.Collect in the Form.Closing or other appropriate event). Only call GC.Collect if necessary. – Scott Hutchinson Jan 21 '16 at 03:37
  • Yet another attempt. When you create an ObservableCollection, there is no direct way to set its capacity or to change it after it is created. For the underlying List, however, you can pass the capacity as an argument to the constructor, and later you can set the Capacity property, or call the TrimExcess method to shrink the list to fit its contents. This could be important because as you add items to the list/collection, it might double its capacity (e.g., if its capacity is 64, adding an item could increase it to 128). [cont'd] – Scott Hutchinson Jan 21 '16 at 17:42
  • The excess capacity could waste a lot of memory. Since I could find no direct way to adjust an ObservableCollection 's capacity, you might try the approach demonstrated below to initialize it, assuming that you know about how many slides will be loaded into the collection. var lst = new List(80); // Create the ObservableCollection by copying lst (w/capacity = 80) var col = new ObservableCollection(lst); – Scott Hutchinson Jan 21 '16 at 17:47
  • Did you end up fixing the issue? if so how did you do it? – GoozMeister Aug 31 '20 at 14:21

0 Answers0