WPF Performance Optimization

WPF Application performance has always been major issue for application developers. As the application size increases , the number of controls, windows, styles , complexity goes on increasing within the application. And slowly and slowly we miss to measure and check how optimised our wpf application is.

In order to solve this problem , it is recommended that the development team has its targets for WPF application in order to keep in check its performance and do the performance tuning iteratively throughout the development phase.

With the application growing , code refactoring and and performance tuning becomes the essential part of the development life cycle.

First and the foremost important point is Data Virtualization. In large enterprise applications , the data application is dealing with is very large and for better UI, we use heavy controls like datagrids which in itself includes convertors , images etc which we use for better graphical representation and i.e not wrong as major reason for opting WPF is usually due to its rich ability to provide rich graphics. But to optimise performance we should take care of small things , use hardcoded width and height wherever possible as it will boost performance in comparison to dynamic width and height where it calculates again and again.

Why is data virtualization important?  When there are hundreds and thousands of records in data source which needs to bind with data grids , listviews or stackpanels etc , the control will process the complete datasource with it even when it is displaying 10 records at a time and will change on scrolling or any other option your application might have given to the user.

So if the developers enable virtualization then the control will process only the viewable portion of data and hence will significantly improve the performance of the application. Only controls that use the ItemsControl class to manage data items can take advantage of the VirtualizingStackPanel. Try to minimise the use of heavy controls inside the items template of grids data layout controls.

For example :- StackPanels , ListBox and Listview supports virtualization by default.  As it implements Visualizing Panel class in WPF.Virtualizing Panel @MSDN

If you want to compare , try out the below snippets of code:

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
         
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         
Title="MainWindow" Height="350" Width="525">
    <Grid>

        <ListBox Name="ListBoxWithVirtualization" Height="100" Margin="5"
         VirtualizingStackPanel.IsVirtualizing="True"
         VirtualizingStackPanel.VirtualizationMode="Recycling" />

        <ListBox Name="ListBoxWithoutVirtualization" Height="100" Margin="5"
                 VirtualizingStackPanel.IsVirtualizing="False" />
         

    </Grid>
</Window>


MainWindow.xaml.cs


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;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
         public MainWindow()
        {
             InitializeComponent();
            BindListBoxWithoutVirualization();
        }

         public void BindListBoxWithVirualization()
        {

            ListBoxWithoutVirtualization.Visibility = Visibility.Hidden ;          

             for (int i = 0; i< 1000000; i++)
                 ListBoxWithVirtualization.Items.Add("Item - " + i.ToString());

        }

        public void BindListBoxWithoutVirualization()
        {

              ListBoxWithVirtualization.Visibility = Visibility.Hidden ;
             for (int i = 0; i < 1000000; i++)
                 ListBoxWithoutVirtualization.Items.Add("Item - " + i.ToString());

        }
    }
}

To check what happens when virtualization is enabled , in the constructor call , BindListBoxWithVirualization and run the project , check the CPU usage and then call BindListBoxWithoutVirualization and run the project and see the difference yourself.

The next point is keep the visual tree simple as much as possible. Always give emphasis while designing your xaml , make sure and take maximum care that you keep the visual tree simple. Bind the data source only to the required controls not all in the hierarchy. This scenario occurs usually when we do databinding with controls having item templates.

Tip - Try not to make visual tree heavy in order to reduce your lines of code behind. Heavy Visual Tree will be more costly.

For example :-

<Border>
     <Grid>
<Grid.RowDefinitions >
<Row Definition />
<Row Definition />
< /Grid.RowDefinitions >
<StackPanel Grid.Row=0>
<TextBlock />
</StackPanel>
<StackPanel Grid.Row=1>
<TextBlock />
</StackPanel>
                 </Grid>
</Border>


In the example above StackPanels are extra , so if we remove them, we have better optimised code.

The next thing I am going to discuss is about Dispatcher.BeginInvoke. Every WPF Developer reading this article is familiar about this and when as a developer we start with WPF , it comes as a best practise to use Dispatcher.BeginInvoke to avoid your application to hang and better user experience.But use of multiple Dispatcher.BeginInvokes increases the CPU usage so better batch up your Dispatcher.BeginInvokes in a single call.

To understand this better , we need to understand that each time a Dispatcher.BeginInvoke is called , it gives mainUI thread an asynchronous call to handle the drawing in it. Definitely best practise would be  batching up your points in a queue and then drawing them to the screen at once, rather than trying to have multiple draws running asynchronously on the same thread.

For example, consider the following scenario where in a window you have 3 datagrids (dg1,dg2,dg3) displaying 3 sets of large data from server1, server2, server3 respectively.. Now let us compare :

Code Snippet1 :-
private void Binddata()
{
App.Current.Dispatcher.BeginInvoke(() =>
{
dg1.ItemsSource = null ;
dg1.ItemSource =  //GetDataFromServer1;
});

App.Current.Dispatcher.BeginInvoke(() =>
{
dg2.ItemsSource = null ;
dg2.ItemSource =  //GetDataFromServer1;
});
App.Current.Dispatcher.BeginInvoke(() =>
{
dg3.ItemsSource = null ;
dg3.ItemSource =  //GetDataFromServer1;
});
}

Now better approach would be

private void Binddata()
{

List
<T> data1 =  //GetDataFromServer1;
List
<T> data2 =  //GetDataFromServer2;
List
<T> data3 =  //GetDataFromServer3;



App.Current.Dispatcher.BeginInvoke(() =>
{
dg1.ItemsSource = null ;
dg1.ItemSource = data1;

App.Current.Dispatcher.BeginInvoke(() =>
{
dg2.ItemsSource = null ;
dg2.ItemSource =  data2;
});
App.Current.Dispatcher.BeginInvoke(() =>
{
dg3.ItemsSource = null ;
dg3.ItemSource =  data3;
});
}

Code Snippet 3:
best would be
private void Binddata()
{

List
<T> data1 =  //GetDataFromServer1;
List
<T> data2 =  //GetDataFromServer2;
List
<T> data3 =  //GetDataFromServer3;



App.Current.Dispatcher.BeginInvoke(() =>
{
dg1.ItemsSource = null ;
dg1.ItemSource = data1;
dg2.ItemsSource = null ;
dg2.ItemSource =  data2;
dg3.ItemSource=null;
dg3.ItemSource = data3;
});

}

Last but not the least beware of memory leaks in your application. In order to find the points of memory leaks in your application using any of WPF profiling tool and follow the instructions and possible causes and refactor your code accordingly.

By Kyurius    Popularity  (4840 Views)