Important Performance Issue (2nd post)

Aug 13, 2009 at 8:02 PM

Hi,

I have an important performance issue that I had already pointed out through this post http://wpf.codeplex.com/Thread/View.aspx?ThreadId=64524 .

I am just trying to refresh a datagrid (10 columns) each 500ms and each 500ms I add 20 items to the datagrid.

The program monopolizes a lot of CPU usage, as you can monitor it and judge by yourself. With an Intel Core duo 2 Ghz and 3Gb DDR2, the program uses 40% of my CPU!!!

So after a while, the dataGrid becomes out of control and you cannot event interact with it or close your window.

Please, can anyone help me on this?

I managed to reproduce the problem, below is the simple code.

Thanks,

Kamel

 

 

Xaml code:

<Window x:Class="DG_ListCollectionView.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="1000" Width="1000" xmlns:my="http://schemas.microsoft.com/wpf/2008/toolkit">
    <Grid>
        <my:DataGrid AutoGenerateColumns="True" Name="dataGrid1" />
    </Grid>
</Window>

 

C# code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Timers;

namespace DG_ListCollectionView
{
    public partial class Window1 : Window
    {
        private List<Trade> _ListOfTrade = new List<Trade>();
        private ListCollectionView collectionView;

        public Window1()
        {
            InitializeComponent();

            EditListOfTrade();

            collectionView = new ListCollectionView(_ListOfTrade);

            // We create a timer which will send an event each 500ms to refresh the dataGrid
            Timer aTimer = new Timer();
            aTimer.Elapsed += new ElapsedEventHandler(aTimer_Elapsed);
            aTimer.Interval = 500;
            aTimer.Enabled = true;

            dataGrid1.ItemsSource = collectionView;
        }

        void aTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            EditListOfTrade();

            dataGrid1.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                  (System.Threading.ThreadStart)delegate()
                  {
                      collectionView.Refresh();
                  }
                  );
        }


        void EditListOfTrade()
        {
            for (int i = 0; i < 20; i++)
            {
                Trade trade = new Trade();
                _ListOfTrade.Add(trade);
            }
        }
    }

    class Trade
    {
        private Random random = new Random();

        public double Field1 { get { return random.NextDouble(); } }
        public double Field2 { get { return random.NextDouble(); } }
        public double Field3 { get { return random.NextDouble(); } }
        public double Field4 { get { return random.NextDouble(); } }
        public double Field5 { get { return random.NextDouble(); } }
        public double Field6 { get { return random.NextDouble(); } }
        public double Field7 { get { return random.NextDouble(); } }
        public double Field8 { get { return random.NextDouble(); } }
        public double Field9 { get { return random.NextDouble(); } }
        public double Field10 { get { return random.NextDouble(); } }
    }
}

Coordinator
Sep 1, 2009 at 8:49 PM

Hi kawone,

Refreshing the entire collection is extremely expensive, because the datagrid now needs to throw away and recreate all of its rows and all of its bindings.  A much faster way is to implement INotifyPropertyChanged on anything you're displaying in the UI, and then raise the PropertyChanged event when the item needs to be updated.  Also, for binding to collections, INotifyCollectionChanged (which ObservableCollection gives you for free) will tell the UI whenever items in the list are added and removed, allowing them to be updated individually instead of needing to recreate the UI for the entire collection.

This updated code runs much, much faster.  Let me know if you have any problems or questions about it.  Thanks!!

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Timers;
using System.ComponentModel;

namespace DG_ListCollectionView
{
    public partial class Window1 : Window
    {
        private List<Trade> _ListOfTrade = new List<Trade>();
        private ListCollectionView collectionView;

        public Window1()
        {
            InitializeComponent();

            EditListOfTrade();

            collectionView = new ListCollectionView(_ListOfTrade);

            // We create a timer which will send an event each 500ms to refresh the dataGrid
            Timer aTimer = new Timer();
            aTimer.Elapsed += new ElapsedEventHandler(aTimer_Elapsed);
            aTimer.Interval = 500;
            aTimer.Enabled = true;

            dataGrid1.ItemsSource = collectionView;
        }

        void aTimer_Elapsed(object sender, ElapsedEventArgs e)
        {
            EditListOfTrade();

            dataGrid1.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                  (System.Threading.ThreadStart)delegate()
                  {
                      //collectionView.Refresh();
                      foreach (Trade trade in _ListOfTrade)
                      {
                          trade.Refresh();
                      }
                  }
                  );
        }


        void EditListOfTrade()
        {
            for (int i = 0; i < 20; i++)
            {
                Trade trade = new Trade();
                _ListOfTrade.Add(trade);
            }
        }
    }

    class Trade : INotifyPropertyChanged
    {
        private Random random = new Random();

        public double Field1 { get { return random.NextDouble(); } }
        public double Field2 { get { return random.NextDouble(); } }
        public double Field3 { get { return random.NextDouble(); } }
        public double Field4 { get { return random.NextDouble(); } }
        public double Field5 { get { return random.NextDouble(); } }
        public double Field6 { get { return random.NextDouble(); } }
        public double Field7 { get { return random.NextDouble(); } }
        public double Field8 { get { return random.NextDouble(); } }
        public double Field9 { get { return random.NextDouble(); } }
        public double Field10 { get { return random.NextDouble(); } }





        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        internal void Refresh()
        {
            if (PropertyChanged != null)
            {
                // Format is PropertyChanged("PropertyName"), where "" is a shortcut for All Properties On This Class
                PropertyChanged(this, new PropertyChangedEventArgs(""));
            }
        }
    }
}

 

Sep 2, 2009 at 2:56 PM
Edited Sep 2, 2009 at 3:03 PM

Hi,

 

In the code above, I was trying to add 20 new trade at each refresh (code in the EditListOfTrade method).

However this is not the result expected as only the 20 first rows are refreshed (the 20 that were in the constructor of the listCollectionView) and no row is added to the datagrid.

Please, can you help me to achieve this?

Sep 2, 2009 at 3:19 PM

Hi,

As you explained above "for binding to collections, INotifyCollectionChanged (which ObservableCollection gives you for free) will tell the UI whenever items in the list are added and removed, allowing them to be updated individually instead of needing to recreate the UI for the entire collection", I tried to replace dataGrid1.ItemsSource = collectionView by dataGrid1.ItemsSource = new ObservableCollection<Trade>(_ListOfTrade) but this is not working.

Can you explain me the logic behind this?

Thanks,

Kamel