This is a migrated thread and some comments may be shown as answers.

Performance degrades while the property grid is placed in RadDocking

11 Answers 189 Views
PropertyGrid
This is a migrated thread and some comments may be shown as answers.
BENN
Top achievements
Rank 1
BENN asked on 20 Sep 2016, 09:36 AM

The performance of the property grid isn't great, and it appears that the performance degrades on some scenarios.

We are placing the property grid inside a RadDocking. The pane itself has the IsHidden bound to a property, since the software is an IDE and depending on the type of the active document, we either show a toolbox and  / or a property grid.

 

I can see with dotTrace that something is leaking (The weakevent listener is raising the handler more time than it should).

 

Code:

MainWindow.xaml:

<Window x:Class="RadPropertyGridPerformanceIssue1.MainWindow"
        xmlns:local="clr-namespace:RadPropertyGridPerformanceIssue1"
        Title="MainWindow" Height="350" Width="525" x:Name="self">
     
    <Grid>
        <telerik:RadDocking>
            <telerik:RadSplitContainer telerik:DockingPanel.InitialSize="228,650"
                    Name="RightContainer" InitialPosition="DockedRight" Orientation="Vertical">
                <telerik:RadPaneGroup>
                    <telerik:RadPane CanUserClose="False" x:Name="propertiesPane" Header="Properties Window"
                                     CanDockInDocumentHost="False" telerik:RadDocking.SerializationTag="Properties"
                                     DataContext="{Binding ElementName=self, Path=DataContext}"
                                     IsHidden="{Binding ElementName=self, Path=IsHidden, Mode=TwoWay}">
                        <local:PropertiesControl DataContext="{Binding}" />
                    </telerik:RadPane>
                </telerik:RadPaneGroup>
            </telerik:RadSplitContainer>
        </telerik:RadDocking
        <StackPanel HorizontalAlignment="Left">
            <Button Click="Button_Click">Toggle Visibility</Button>
            <Button Click="Button_Click_1">Toggle Selected Item</Button>
        </StackPanel>
    </Grid>
</Window>

 

MainWindow,cs

public partial class MainWindow : Window
{
    private bool isItemSet = false;
 
    public object Item
    {
        get { return (object)GetValue(ItemProperty); }
        set { SetValue(ItemProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for Item.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ItemProperty =
        DependencyProperty.Register("Item", typeof(object), typeof(MainWindow), new UIPropertyMetadata(null));
 
 
 
    public bool IsHidden
    {
        get { return (bool)GetValue(IsHiddenProperty); }
        set { SetValue(IsHiddenProperty, value); }
    }
 
    // Using a DependencyProperty as the backing store for IsHidden.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty IsHiddenProperty =
        DependencyProperty.Register("IsHidden", typeof(bool), typeof(MainWindow), new UIPropertyMetadata(false));
 
     
     
    public MainWindow()
    {
        InitializeComponent();
        this.DataContext = this;
    }
 
    private void Button_Click(object sender, RoutedEventArgs e)
    {
        IsHidden = !IsHidden;
    }
 
    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        if (!isItemSet)
            Item = new MyObject();
        else
            Item = null;
 
        isItemSet = !isItemSet;
    }
}

 

MyObject:

public class MyObject
    {
 
        [Display(Name = "Name1", GroupName = "Details1", Order = 5, Prompt = "tttt"), Browsable(true)]
        public string Name1 { get; set; }
 
        [Display(Name = "Name2", GroupName = "Details1", Order = 5, Prompt = "tttt"), Browsable(true)]
        public string Name2 { get; set; }
 
        [Display(Name = "Name3", GroupName = "Details2", Order = 5, Prompt = "tttt"), Browsable(true)]
        public string Name3 { get; set; }
 
        [Display(Name = "Name4", GroupName = "Details2", Order = 5, Prompt = "tttt"), Browsable(true)]
        public string Name4 { get; set; }
 
        [Display(Name = "Name5", GroupName = "Details2", Order = 5, Prompt = "tttt"), Browsable(true)]
        public string Name5 { get; set; }
 
        [Display(Name = "Name6", GroupName = "Details2", Order = 5, Prompt = "tttt"), Browsable(true)]
        public string Name6 { get; set; }
 
        [Display(Name = "Name7", GroupName = "Details2", Order = 5, Prompt = "tttt"), Browsable(true)]
        public string Name7 { get; set; }
 
        [Display(Name = "Name8", GroupName = "Details2", Order = 5, Prompt = "tttt"), Browsable(true)]
        public string Name8 { get; set; }
 
        [Display(Name = "Name9", GroupName = "Details2", Order = 5, Prompt = "tttt"), Browsable(true)]
        public string Name9 { get; set; }
 
        [Display(Name = "Name10", GroupName = "Details2", Order = 5, Prompt = "tttt"), Browsable(true)]
        public string Name10 { get; set; }
 
        [Display(Name = "Name11", GroupName = "Details3", Order = 6, Prompt = "tttt"), Browsable(true)]
        public string Name11 { get; set; }
 
        [Display(Name = "Name12", GroupName = "Details3", Order = 6, Prompt = "tttt"), Browsable(true)]
        public string Name12 { get; set; }
 
        [Display(Name = "Name13", GroupName = "Details3", Order = 6, Prompt = "tttt"), Browsable(true)]
        public string Name13 { get; set; }
 
        [Display(Name = "Name14", GroupName = "Details3", Order = 6, Prompt = "tttt"), Browsable(true)]
        public string Name14 { get; set; }
 
        [Display(Name = "Name15", GroupName = "Details3", Order = 6, Prompt = "tttt"), Browsable(true)]
        public string Name15 { get; set; }
 
        [Display(Name = "Name16", GroupName = "Details3", Order = 6, Prompt = "tttt"), Browsable(true)]
        public string Name16 { get; set; }
 
        [Display(Name = "Name17", GroupName = "Details4", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name17 { get; set; }
 
        [Display(Name = "Name18", GroupName = "Details4", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name18 { get; set; }
 
        [Display(Name = "Name19", GroupName = "Details4", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name19 { get; set; }
 
        [Display(Name = "Name20", GroupName = "Details4", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name20 { get; set; }
 
        [Display(Name = "Name21", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name21 { get; set; }
 
        [Display(Name = "Name22", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name22 { get; set; }
 
        [Display(Name = "Name23", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name23 { get; set; }
 
        [Display(Name = "Name24", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name24 { get; set; }
 
        [Display(Name = "Name25", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name25 { get; set; }
 
        [Display(Name = "Name26", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name26 { get; set; }
 
        [Display(Name = "Name27", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name27 { get; set; }
 
        [Display(Name = "Name28", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name28 { get; set; }
 
        [Display(Name = "Name29", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name29 { get; set; }
 
        [Display(Name = "Name30", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name30 { get; set; }
 
        [Display(Name = "Name31", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name31 { get; set; }
 
        [Display(Name = "Name32", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name32 { get; set; }
 
        [Display(Name = "Name33", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name33 { get; set; }
 
        [Display(Name = "Name34", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name34 { get; set; }
 
        [Display(Name = "Name35", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name35 { get; set; }
 
        [Display(Name = "Name36", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name36 { get; set; }
 
        [Display(Name = "Name37", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name37 { get; set; }
 
        [Display(Name = "Name38", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name38 { get; set; }
 
        [Display(Name = "Name39", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name39 { get; set; }
 
        [Display(Name = "Name40", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name40 { get; set; }
 
        [Display(Name = "Name41", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name41 { get; set; }
 
        [Display(Name = "Name42", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name42 { get; set; }
 
        [Display(Name = "Name43", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name43 { get; set; }
 
        [Display(Name = "Name44", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name44 { get; set; }
 
        [Display(Name = "Name45", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name45 { get; set; }
 
        [Display(Name = "Name46", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name46 { get; set; }
 
        [Display(Name = "Name47", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name47 { get; set; }
 
        [Display(Name = "Name48", GroupName = "Details5", Order = 7, Prompt = "tttt"), Browsable(true)]
        public string Name48 { get; set; }
    }

 

PropertiesControl.xaml:

<UserControl x:Class="RadPropertyGridPerformanceIssue1.PropertiesControl"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:telerk="http://schemas.telerik.com/2008/xaml/presentation"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <telerk:RadPropertyGrid Item="{Binding Item}" DescriptionPanelVisibility="Collapsed"/>
    </Grid>
</UserControl>

 

If you click the first button and then the second button over and over again, then you would notice that the property grid becomes slower and slower, and that the Telerik.Windows.Data.WeakEvent+WeakListener`1.Handler() is being called more than once, event though OnCollectionChanged was called only once.

 

On Telerik 2016.Q2 the problem was even worse. The OnCollectionChanged would be called as the number of properties the object has.

 

 

 

 

11 Answers, 1 is accepted

Sort by
0
BENN
Top achievements
Rank 1
answered on 20 Sep 2016, 09:47 AM

Btw, the way, I also tried the following, as a workaround:

There is a data templates that shows the PropertiesControl for a specific data context

<telerik:RadPaneGroup IsContentPreserved="True">
    <telerik:RadPane CanUserClose="False" x:Name="propertiesPane" Header="Properties Window"
                     CanDockInDocumentHost="False" telerik:RadDocking.SerializationTag="Properties"
                     DataContext="{Binding ElementName=self, Path=DataContext.PropertiesViewModel}"
                     Content="{Binding ElementName=self, Path=DataContext.PropertiesViewModel}"
                     IsHidden="{Binding ElementName=self, Path=DataContext.PropertiesViewModel.IsHidden, Mode=TwoWay}"
                     IsPinned="{Binding ElementName=self, Path=DataContext.PropertiesViewModel.IsPinned, Mode=TwoWay}">
    </telerik:RadPane>
</telerik:RadPaneGroup>

 

The problem with this workaround is that each time the tab gets hidden or gets pinned and unpinned, then a new instance of the properties control is being created. Because the program consumes a large amount of ram, then a lot of objects are already allocated in Gen1 and Gen2, so the new instances are not being collected immediately.

 

So I see that the ItemChanged event is being called more than once when I change the item, and this also degrades the performance (I sometimes see a 2-3 seconds delay when binding to a 25 properties object, where normally, the delay is about 0.5 second, which is also not great!!)

 

 

 

0
Ivan Ivanov
Telerik team
answered on 21 Sep 2016, 01:21 PM
Hello Benn,

I believe that I managed to create a similar project following your guidelines. I tested it with Q2 SP, as you mentioned that the performance with Q2 was poorer. 
I tested the following scenario: Change the selected items repeatedly and Hide/Show the pane a few times. During those tests, I did not observe a significant change in the performance. I also ran a memory profiler and while the application rapidly amassed big memory footprint, when changing the item, it was always reduced to the initial state after invoking GC.Collect.
Can you please have a look at the attached project to check whether there are any distinctive differences between your aproach and mine? Can you also confirm which version is the version that you are currently using? 

Regards,
Ivan Ivanov
Telerik by Progress
Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
0
BENN
Top achievements
Rank 1
answered on 21 Sep 2016, 03:06 PM

Ok,

Your example follows my instructions. For automating things, I have added another button:

<Button Click="Button_Click_2">Repeat for 2000 times</Button>

 

And the code behind is:

private void Button_Click_2(object sender, RoutedEventArgs e)
{
    // Shoule cause the handler to be called 1000 more times.
 
    for (int i = 0; i < 2000; i++)
    {
        Action action = delegate()
        {
            Button_Click(null, null);
        };
        Dispatcher.BeginInvoke(action, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
 
        action = delegate()
        {
            Button_Click_1(null, null);
        };
        Dispatcher.BeginInvoke(action, System.Windows.Threading.DispatcherPriority.ApplicationIdle);
    }
}

 

You should consider having much less that 2000 iterations (because it will take time...)

You would see that the show and hide becomes slower and slower. I'm having the code called with BeginInvoke, so the UI will update accordingly (and the view will be unloaded and reloaded... which is, by looking at your code, when the weakevents are subscribed and unsubscribed).

 

I didn't have the patience on waiting for the process to complete, so here is a result after about 330 iterations (see images)

 

 

 

0
Stefan
Telerik team
answered on 26 Sep 2016, 11:51 AM
Hello Benn,

Thanks for the update.

I modified the previously attached sample application with your code. I made a test with 2000 iterations and still nor the performance got slower, neither unreleased memory was present after GC.Collect. Can you please check out the attached snapshot?

Am I missing something here?

Regards,
Stefan X1
Telerik by Progress
Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
0
BENN
Top achievements
Rank 1
answered on 26 Sep 2016, 05:47 PM

Will, i don't call GC.Collect. 

My screenshots speek for themeselves. Dottrace clearly shows that the handler is being called more than it should as the property grid loads and in loads (there seems to be some kind of weak events leak, which causing the weak event to be kept registered to the collection changed).

0
Accepted
Ivan Ivanov
Telerik team
answered on 29 Sep 2016, 04:45 PM
Hi Benn,

I managed to reproduce the reported behavior on our side. Generally, there is an internal cache for the weak event subscriptions, so that their count should not grow, but it seems that closing the Pane breaks this logic. This results in creating a new subscription after every iteration. I am adding 1000 Telerik points to you account for this bug report. Here is a public item for the bug that you can follow to track our progress with solving the issue.

Regards,
Ivan Ivanov
Telerik by Progress
Do you need help with upgrading your AJAX, WPF or WinForms project? Check the Telerik API Analyzer and share your thoughts.
0
BENN
Top achievements
Rank 1
answered on 30 Sep 2016, 07:00 PM
Thanks
0
BENN
Top achievements
Rank 1
answered on 02 Nov 2020, 02:09 PM

The issue is back. It wasn't in 2019 Q2, but I can see that 2020 Q2 already have it. The issue might have happened due to the fix on 2020.1.115:

Memory leak when the Item property is changed multiple times runtime.

 

I have the property grid in a rad docking pane. Property grid is hidden for documents that don't need a property grid, and before that, the Item is being set to null.

As a result:

OnItemPropertyChanged is being called, which calls ResetSubsriptions

ResetSubsriptions clears CollectionChangedSubscriptions and ItemChangedSubscriptions

 

The when OnLoad happens, then ClearSubscriptions is being called.

However:

if (this.ItemChangedSubscriptions != null)
            {
                WeakEvent.WeakEventToken<ItemChangedEventArgs<PropertyDefinition>> weakEventToken = null;
                if (this.ItemChangedSubscriptions.TryGetValue(this.PropertyDefinitions, out weakEventToken) && weakEventToken != null)
                {
                    weakEventToken.Unsubscribe();
                    this.ItemChangedSubscriptions.Remove(this.PropertyDefinitions);
                }
            }
            if (this.CollectionChangedSubscriptions != null)
            {
                WeakEvent.WeakEventToken<NotifyCollectionChangedEventArgs> weakEventToken2 = null;
                if (this.CollectionChangedSubscriptions.TryGetValue(this.PropertyDefinitions, out weakEventToken2) && weakEventToken2 != null)
                {
                    weakEventToken2.Unsubscribe();
                    this.CollectionChangedSubscriptions.Remove(this.PropertyDefinitions);
                }
            }

 

weakEventToken and weakEventToken2 will be null, since the ItemChangedSubscriptions and CollectionChangedSubscriptions where cleared.

Our customers are already complaining about it.

 

Please fix ASAP, or suggest a fix that I could apply and recompile.

 

One suggestion I have is to change the ResetSubscriptions code in the ItemChanged to ClearSubscriptions, or at least call it before the ResetSubscriptions, but I'm not sure if it will have the desired result without having some side effects (like performance degradation, or things not working correctly on some situations).

 

Please advise.

 

Thanks.

0
BENN
Top achievements
Rank 1
answered on 02 Nov 2020, 02:26 PM

Another option I'm thinking of is on ResetSubscriptions get the 2 tokens first, and add them again after  the collections were cleared.

I'm not sure what was the nature of the memory leak that you've fixed in 2020.1.115 so I'm not sure it this "hack" will make the memory leak back or not, and I'm not sure this solution is a good idea either.

 

0
Dilyan Traykov
Telerik team
answered on 04 Nov 2020, 04:30 PM

Hello Benn,

Thank you very much for the detailed description of the issue and the proposed fixes. As a thank you for bringing this to our attention, I've awarded you with some Telerik points.

I've gone ahead and replaced the call of ResetSubscriptions with ClearSubscriptions and tested that this seems to resolve both issues.

The fix should be available with our next internal build on Monday (09.11) if it passes our testing process and does not result in any other side effects. I will update you accordingly if this happens, but if not - please let me know once you manage to test it out.

Regards,
Dilyan Traykov
Progress Telerik

Virtual Classroom, the free self-paced technical training that gets you up to speed with Telerik and Kendo UI products quickly just got a fresh new look + new and improved content including a brand new Blazor course! Check it out at https://learn.telerik.com/.

0
BENN
Top achievements
Rank 1
answered on 05 Nov 2020, 02:15 PM

I compiled the dlls with the fix, and it solves the performance issue.

Thanks.

Tags
PropertyGrid
Asked by
BENN
Top achievements
Rank 1
Answers by
BENN
Top achievements
Rank 1
Ivan Ivanov
Telerik team
Stefan
Telerik team
Dilyan Traykov
Telerik team
Share this question
or