3

At a certain point in my WPF application, the UI thread will lock up for ~0 till ~500 ms (depending on the amount) once I remove and/or add items to a Canvas. Several performance tests pointed to Canvas.Children.Remove for the main cause, and .Add as well (.Remove is much more severe). I remove about 500 items at the same time, and add about 500 items at the same time as well. This about 10 times per second causes issues.

In the end, I've written a simple benchmarking application, code below.

    public MainWindow() {
        InitializeComponent();
        this.Loaded += (object sender, RoutedEventArgs e) => {
            List<Image> a = new List<Image>();
            Stopwatch s = new Stopwatch();
            s.Start();
            for (int i = 0; i < 7500; i++) {
                Image img = new Image();
                Canvas.SetLeft(img, i * 10);
                C.Children.Add(img);
                if (i % 10 == 1)
                    a.Add(img);
            }
            s.Stop();
            long add = s.ElapsedMilliseconds;
            s.Reset();
            s.Start();
            foreach (Image img in a) {
                C.Children.Remove(img);
            }
            s.Stop();
            MessageBox.Show(String.Format("Add: {0}, Remove: {1}.", add, s.ElapsedMilliseconds));
        };
    }

This gives the following result (around this, every time I run it):

Add: 174, Remove: 156.

Keep in mind only 750 items get removed, while 7500 get added.

The process is a bit heavy, but I don't want the UI (ScrollViewer mainly) to lock up while it's doing this. Another problem I face is that I can't "just move" this to another Thread, since I can't control the UI from that specific Thread.

How to improve this process? Or is there a way to dynamically add/remove items "over time" so it does not hang up?

Thanks,

~Tgys

Simon Sarris
  • 62,212
  • 13
  • 141
  • 171
Tgys
  • 612
  • 1
  • 11
  • 21
  • 1
    Have you considered virtualization? http://stackoverflow.com/questions/876811/wpf-virtualizing-a-canvas – Josh C. Sep 20 '12 at 13:45
  • What are you trying to do with adding or removing so many items so frequently? Would it be quicker or easier drawing to a bitmap and showing that instead? – akton Sep 20 '12 at 13:56
  • @JoshC. Checking out virtualization soon, seems interesting. – Tgys Sep 20 '12 at 14:08
  • @akton I'm building some kind of 2D editor (non-tile based, found an article about virtualisation for that before), this workspace is quite huge, so I want to dynamically load/unload images from memory. (Displaying about 10 million images (Probably more actually, rough guess) "out of scroll bounds" doesn't really work ;)). – Tgys Sep 20 '12 at 14:11
  • Just to be a bit more clear, virtualization does not save you from loading data into the app. Virtualization saves you from rendering stuff not in view. If you are trying to load data into the application only on demand, you may want to look into lazy loading techniques. – Josh C. Sep 20 '12 at 14:12
  • @JoshC. Well, I already load data from a file dynamically on a secondary thread, which isn't the problem. The problem is adding/removing Image objects dynamically at run-time, so they aren't visible out of range (and also removed from memory, in the end). If virtualisation would load ALL 10 million images at startup, that would obviously not work well. – Tgys Sep 20 '12 at 14:24
  • Virtualizatoin would not load all 10 million images at startup. Your application layer is responsible for fetching data. Your presentation layer is responsible for rendering. Virtualization only renders what would be visible and avoids drawing stuff that is off-screen. – Josh C. Sep 20 '12 at 20:27

1 Answers1

2

What if you split this into new thread and dispatch the work into UI thread with every 20ms or so? This way you wont completely lock the UI up. So the user could do other things in meanwhile.

Other things I know: Is there a AddRange method? Perhaps you could add all items at once. This way only one layout update would theoretically happen cutting the time into zero.

Same for Remove. See Canvas.Children.RemoveRange(a);

Erti-Chris Eelmaa
  • 25,338
  • 6
  • 61
  • 78
  • Your first suggestion could work for me, will experiment a bit onto that. My biggest concern, however, is whenever there are many more items to be removed/added, taking tons of time, 'clogging' the queue. Your second suggestion rolls onto a problem: I remove 'random' items, and I add 'random' items. Those are not indexed, and sometimes a part of the added items is being removed, messing the indices up, I guess. – Tgys Sep 20 '12 at 14:25
  • I didn't quite understand. It really doesn't matter. Feed the list a into RemoveRange and it will remove all items it can. Updating layout only once. Also, get dotTrace, that's gonna be more accurate. It shows internally where is the bottleneck. – Erti-Chris Eelmaa Sep 21 '12 at 09:36